Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running method in the background and UI Thread WPF

I'm having troubles with the following example:

public void Method()
{
  LongRunningMethod();
}

LongRunningMethod() takes around 5 seconds to invoke. I am invoking Method() from the UI thread, so it obviously should freeze the UI. The solution for that is to run Method() within a new Task so I am running it like this:

Task.Factory.StartNew(()=>{Method()})

It's still blocking the UI so I thought whether LongRunningMethod() is using the UI context probably. Then I tried another solution:

new Thread(()=>Method()).Start()

and it started working. How is that possible? I know that Task is not guaranteed to be run on a different thread but CLR should be smart enough to figure out that it's long running method.

like image 721
MistyK Avatar asked Dec 10 '15 08:12

MistyK


2 Answers

You are scheduling work on the User Interface (UI) Thread cause you are using TaskScheduler.FromCurrentSynchronizationContext()) in this code:

Task nextTask = task.ContinueWith(x =>
   {
       DoSomething();
   }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());

       task.Start();
     }

And this is a reason why your UI is frozen. To prevent try to change TaskScheduler to Default:

Task task = Task.Run(() => {; });
Task nextTask = task.ContinueWith(x =>
{
   //DoSomething();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);

Task.Factory.StartNew is dangerous cause it uses TaskScheduler.Current as opposed to TaskScheduler.Default. To prevent this use Task.Run which always points to TaskScheduler.Default. Task.Run is new in .NET 4.5, if you're in .NET 4.0 you can create your TaskFactory with default parameters.

As MSDN says:

TaskScheduler.FromCurrentSynchronizationContext()) means schedule a task on the same thread that the user interface (UI) control was created on.

Update:

What happens when you run method RunTask():

  1. var task = new Task(action, cancellationTokenSource.Token);

    create a "task". (task is not run. The "task" is just queed to the ThreadPool.)

  2. Task nextTask = task.ContinueWith(x => { DoSomething(); }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());

create a "nextTask" which will start performing AFTER "task" is completed and the "nextTask" will be performed on UI thread as you've set a feature TaskScheduler.FromCurrentSynchronizationContext().

  1. task.Start();

You run your "task". When the "task" is completed, then "nextTask" is run by method "task.ContinuuWith()" which will be performed on UI thread you wrote (TaskScheduler.FromCurrentSynchronizationContext()

So to sum up, the two your tasks are interconnected and continuation of task is performed on UI thread which is a reason to freeze your UI. To prevent this behavior use TaskScheduler.Default.

like image 145
StepUp Avatar answered Oct 20 '22 19:10

StepUp


This is exactly how it looks like:

 public void RunTask(Action action){
   var task = new Task(action, cancellationTokenSource.Token);
 Task nextTask = task.ContinueWith(x =>
   {
       DoSomething();
   }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());

       task.Start();
     }

public void DoSomething()
{
    if(condition) // condition is true in this case (it's recurency but not permanent)
    RunTask(() => Method()); // method is being passed which blocks UI when invoked in RunTask method
}

public void Method()
{
  LongRunningMethod();
}

This is the starting point invocation (UI Thread):

  RunTask(()=>Action());
like image 22
MistyK Avatar answered Oct 20 '22 20:10

MistyK