I current working an application that reads from a large binary file, which holds several thousands files, every file is being processed by some other class in application. This class returns either an object or null. I want to show a progress on the main form but for some reason I can't get my head around it.
int TotalFound = 0;
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext;
BufferBlock<file> buffer = new BufferBlock<file>();
DataflowBlockOptions options = new DataflowBlockOptions(){ TaskScheduler = uiScheduler, };
var producer = new ActionBlock<someObject>(largeFile=>{
var file = GetFileFromLargeFile(largeFile);
if(file !=null){
TotalFound++;
buffer.post(file);
lblProgress.Text = String.Format("{0}", TotalFound);
}
}, options);
The above code freezes my Form, even do I use "TaskScheduler.FromCurrentSynchronizationContext", why? Because when I use the code below my form updates fine
DataflowBlockOptions options = new DataflowBlockOptions(){ TaskScheduler = uiScheduler, };
var producer = new ActionBlock<someObject>(largeFile=>{
var file = GetFileFromLargeFile(largeFile);
if(file !=null){
Task.Factory.StartNew(() => {
TotalFound++;
buffer.Post(file);
}).ContinueWith(uiTask => {
lblProgress.Text = String.Format("{0}", TotalFound);
},CancellationToken.None, TaskContinuationOptions.None, uiScheduler);
}
});
I am new to this whole TPL Dataflow, so I am hoping someone could share some light on why in the second code snippet it works and in the first snippet it doesn't.
Kind regards, Martijn
The reason your UI is blocked is becase you're using FromCurrentSynchronizationContext
. It causes the code to run on the UI thread, which means it will freeze if you're doing some long running action (most likely GetFileFromLargeFile()
).
On the other hand, you have to run the lblProgress.Text
on the UI thread.
I'm not sure you should set lblProgress.Text
directly in this code, it look like too tight coupling to me. But if you want to do that, I think you should run just that line on the UI thread:
var producer = new ActionBlock<someObject>(async largeFile =>
{
var file = GetFileFromLargeFile(largeFile);
if (file != null)
{
TotalFound++;
await buffer.SendAsync(file);
await Task.Factory.StartNew(
() => lblProgress.Text = String.Format("{0}", TotalFound),
CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
});
But even better solution would be if you made GetFileFromLargeFile()
asynchronous and made sure it doesn't do any long-running actions on the UI thread (ConfigureAwait(false)
can help you with that). If you did that, the code of the ActionBlock
could run on the UI thread without freezing your UI:
var producer = new ActionBlock<someObject>(async largeFile =>
{
var file = await GetFileFromLargeFile(largeFile);
if (file != null)
{
TotalFound++;
await buffer.SendAsync(file);
lblProgress.Text = String.Format("{0}", TotalFound)
}
}, options);