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.
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():
var task = new Task(action, cancellationTokenSource.Token);
create a "task". (task is not run. The "task" is just queed to the ThreadPool.)
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()
.
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
.
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());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With