Question

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

Was it helpful?

Solution

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);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top