I have log tailing app which is executing Background worker thread in infinitive loop and checking for latest entries inside some TXT file. Once it find new entries I use Dispatcher.Invoke to update TextBox on the screen with latest entry added to the text file.
The problem is that if the source text file is being updated constantly every millisecond the user interface is just freezing since Dispatcher.Invoke is updating Textbox so often.
Wonder if there is any workaround. I could get bigger chunks from text file but don't want to spoil log tailing real-time experience, also don't want to delay writing of lines to TextBox by UI thread as this will get my application out of sync with actual data inside source text file.
Here is the Background worker DoWork method
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
reader = new StreamReader(new FileStream(file.File, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
lastMaxOffset = reader.BaseStream.Length;
while (true)
{
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
if (reader.BaseStream.Length == lastMaxOffset)
continue;
reader.BaseStream.Seek(lastMaxOffset, SeekOrigin.Begin);
string line = "";
while ((line = reader.ReadLine()) != null)
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)(() =>
{
textLog.Text += "\r" + line;
scrollViewer.ScrollToBottom();
}));
lastMaxOffset = reader.BaseStream.Position;
}
}
as you can see UI thread is just simply append text to the TextBox and when it happen to often interface freeze
Using Dispatcher.Invoke
is a synchronous call, so it is effectively the same as doing this on the UI thread, because the call to Invoke
blocks until the UI performs the requested task. If you're doing that too much in a short time span you're effectively blocking the UI thread.
Instead, you should use Dispatcher.BeginInvoke
which queues up the work for the UI thread to do, but doesn't block. This will be marginally better, as if you're doing this too much in a short period of time, you're still flooding the UI thread with work to do an it's going to spend a lot of time doing that work.
Instead, what best approach would be is queue up those changes to the UI thread, and then when the queue reaches a defined limit (ie. 100 new lines of text) or exceeds a specific amount of time (say 200ms) then call Dispatcher.BeginInvoke
to send those changes to the UI. This will give you the best UI responsiveness.
By calling Dispatcher.Invoke
for every line you read, you're effectively causing each line to push the data back to the UI thread, and wait for it to complete.
This will likely make the entire routine slower than just using the UI thread directly, as you're adding overhead, but not pulling the bulk of the work into a background thread.
In order to have this speed up, you'll need to do something that buffers the data, and sends it in larger chunks. In this case, since you're looking for new log entries, I'd recommend reading ALL of the log entries at the end of the file at once, and marshalling the entire chunk back to your UI (instead of doing it line by line).
sorry but reading the file in an endless loop is both erroneous and ... well ... dumb.
There are well-defined Classes like FileSystemWatcher, there is no need to put 100% load on your CPU-core just because you want realtime-updates of your file.
I take it you're quite new to programming - well, we all make mistakes and have to learn, accept this advices :
- Endless loops are automatically programming-mistakes
- If loops really are required, one would be advised to let the according thread sleep between every iteration
I agree with the answer by CodingGorilla.
Using Dispatcher.Invoke will result in a synchronous call.
This will have the same result as doing the call directly on the UI thread.
The call will block until the UI performs the requested task and if this happens in rapid succession your UI thread will block and hence you will get no updates.
Try to replace your Dispatcher.Invioke with Dispatcher.BeginInvoke and see if this solves your issue.
// Load in background
this.Dispatcher.BeginInvoke(new Action(() =>
{
textLog.Text += "\r" + line;
scrollViewer.ScrollToBottom();
}));
Also as suggested you might want to put the loop to sleep for period of 1 millisecond; using Thread.Sleep(1); every so often so that you do not end up hogging the cpu.