What is the difference between Task.Run() and Task.Factory.StartNew() - c#

I have Method :
private static void Method()
{
Console.WriteLine("Method() started");
for (var i = 0; i < 20; i++)
{
Console.WriteLine("Method() Counter = " + i);
Thread.Sleep(500);
}
Console.WriteLine("Method() finished");
}
And I want to start this method in a new Task.
I can start new task like this
var task = Task.Factory.StartNew(new Action(Method));
or this
var task = Task.Run(new Action(Method));
But is there any difference between Task.Run() and Task.Factory.StartNew(). Both of them are using ThreadPool and start Method() immediately after creating instance of the Task. When we should use first variant and when second?

The second method, Task.Run, has been introduced in a later version of the .NET framework (in .NET 4.5).
However, the first method, Task.Factory.StartNew, gives you the opportunity to define a lot of useful things about the thread you want to create, while Task.Run doesn't provide this.
For instance, lets say that you want to create a long running task thread. If a thread of the thread pool is going to be used for this task, then this could be considered an abuse of the thread pool.
One thing you could do in order to avoid this would be to run the task in a separate thread. A newly created thread that would be dedicated to this task and would be destroyed once your task would have been completed. You cannot achieve this with the Task.Run, while you can do so with the Task.Factory.StartNew, like below:
Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);
As it is stated here:
So, in the .NET Framework 4.5 Developer Preview, we’ve introduced the
new Task.Run method. This in no way obsoletes Task.Factory.StartNew,
but rather should simply be thought of as a quick way to use
Task.Factory.StartNew without needing to specify a bunch of
parameters. It’s a shortcut. In fact, Task.Run is actually
implemented in terms of the same logic used for Task.Factory.StartNew,
just passing in some default parameters. When you pass an Action to
Task.Run:
Task.Run(someAction);
that’s exactly equivalent to:
Task.Factory.StartNew(someAction,
CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

People already mentioned that
Task.Run(A);
Is equivalent to
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
But no one mentioned that
Task.Factory.StartNew(A);
Is equivalent to:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);
As you can see two parameters are different for Task.Run and Task.Factory.StartNew:
TaskCreationOptions - Task.Run uses TaskCreationOptions.DenyChildAttach which means that children tasks can not be attached to the parent, consider this:
var parentTask = Task.Run(() =>
{
var childTask = new Task(() =>
{
Thread.Sleep(10000);
Console.WriteLine("Child task finished.");
}, TaskCreationOptions.AttachedToParent);
childTask.Start();
Console.WriteLine("Parent task finished.");
});
parentTask.Wait();
Console.WriteLine("Main thread finished.");
When we invoke parentTask.Wait(), childTask will not be awaited, even though we specified TaskCreationOptions.AttachedToParent for it, this is because TaskCreationOptions.DenyChildAttach forbids children to attach to it. If you run the same code with Task.Factory.StartNew instead of Task.Run, parentTask.Wait() will wait for childTask because Task.Factory.StartNew uses TaskCreationOptions.None
TaskScheduler - Task.Run uses TaskScheduler.Default which means that the default task scheduler (the one that runs tasks on Thread Pool) will always be used to run tasks. Task.Factory.StartNew on the other hand uses TaskScheduler.Current which means scheduler of the current thread, it might be TaskScheduler.Default but not always. In fact when developing Winforms or WPF applications it is required to update UI from the current thread, to do this people use TaskScheduler.FromCurrentSynchronizationContext() task scheduler, if you unintentionally create another long running task inside task that used TaskScheduler.FromCurrentSynchronizationContext() scheduler the UI will be frozen. A more detailed explanation of this can be found here
So generally if you are not using nested children task and always want your tasks to be executed on Thread Pool it is better to use Task.Run, unless you have some more complex scenarios.

See this blog article that describes the difference. Basically doing:
Task.Run(A)
Is the same as doing:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

The Task.Run got introduced in newer .NET framework version and it is recommended.
Starting with the .NET Framework 4.5, the Task.Run method is the
recommended way to launch a compute-bound task. Use the StartNew
method only when you require fine-grained control for a long-running,
compute-bound task.
The Task.Factory.StartNew has more options, the Task.Run is a shorthand:
The Run method provides a set of overloads that make it easy to start
a task by using default values. It is a lightweight alternative to the
StartNew overloads.
And by shorthand I mean a technical shortcut:
public static Task Run(Action action)
{
return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}

According to this post by Stephen Cleary, Task.Factory.StartNew() is dangerous:
I see a lot of code on blogs and in SO questions that use Task.Factory.StartNew to spin up work on a background thread. Stephen Toub has an excellent blog article that explains why Task.Run is better than Task.Factory.StartNew, but I think a lot of people just haven’t read it (or don’t understand it). So, I’ve taken the same arguments, added some more forceful language, and we’ll see how this goes. :)
StartNew does offer many more options than Task.Run, but it is quite dangerous, as we’ll see. You should prefer Task.Run over Task.Factory.StartNew in async code.
Here are the actual reasons:
Does not understand async delegates. This is actually the same as
point 1 in the reasons why you would want to use StartNew. The problem
is that when you pass an async delegate to StartNew, it’s natural to
assume that the returned task represents that delegate. However, since
StartNew does not understand async delegates, what that task actually
represents is just the beginning of that delegate. This is one of the
first pitfalls that coders encounter when using StartNew in async
code.
Confusing default scheduler. OK, trick question time: in the
code below, what thread does the method “A” run on?
Task.Factory.StartNew(A);
private static void A() { }
Well, you know it’s a trick question, eh? If you answered “a thread
pool thread”, I’m sorry, but that’s not correct. “A” will run on
whatever TaskScheduler is currently executing!
So that means it could potentially run on the UI thread if an operation completes and it marshals back to the UI thread due to a continuation as Stephen Cleary explains more fully in his post.
In my case, I was trying to run tasks in the background when loading a datagrid for a view while also displaying a busy animation. The busy animation didn't display when using Task.Factory.StartNew() but the animation displayed properly when I switched to Task.Run().
For details, please see https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

Apart from the similarities i.e. Task.Run() being a shorthand for Task.Factory.StartNew(), there is a minute difference between their behaviour in case of sync and async delegates.
Suppose there are following two methods:
public async Task<int> GetIntAsync()
{
return Task.FromResult(1);
}
public int GetInt()
{
return 1;
}
Now consider the following code.
var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());
Here both sync1 and sync2 are of type Task<int>
However, difference comes in case of async methods.
var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());
In this scenario, async1 is of type Task<int>, however async2 is of type Task<Task<int>>

Related

Better solution for sync/async problem in desktop app?

I have WinForms app where button click calls some async method of external library.
private async void button1_Click(object sender, EventArgs e)
{
await CallLibraryAsync();
}
private static async Task CallLibraryAsync()
{
var library = new Library();
await library.DoSomethingAsync();
}
The library looks like this:
public class Library
{
public async Task DoSomethingAsync()
{
Thread.Sleep(2000);
await Task.Delay(1000).ConfigureAwait(false);
// some other code
}
}
Before any asynchronous code there is some calculation simulated by Thread.Sleep call. In that case this call will block UI thread for 2 seconds. I have no option to change the code in DoSomethingAsync.
If I want to solve blocking problem, I could call the library in Task.Run like this:
private static async Task CallLibraryAsync()
{
var library = new Library();
// added Task.Run
await Task.Run(() => library.DoSomethingAsync());
}
It solves the problem, UI is not blocke anymore, but I've consumed one thread from ThreadPool. It is not good solution.
If I want to solve this problem without another thread, I can do something like this:
private static async Task CallLibraryAsync()
{
var library = new Library();
// added
await YieldOnlyAsync().ConfigureAwait(false);
await library.DoSomethingAsync();
}
// added
private static async Task YieldOnlyAsync()
{
await Task.Yield();
}
This solution works. Task.Yield() causes that method YieldOnlyAsync() always runs asynchronously and ConfigureAwait(false) causes that next code (await library.DoSomethingAsync();) runs on some ThreadPool thread, not UI thread.
But it is quite complicated solution. Is there any simpler?
Edit:
If the library method looks like this
public class Library
{
public async Task DoSomethingAsync()
{
await Task.Delay(1000).ConfigureAwait(false);
Thread.Sleep(2000);
await Task.Delay(1000);
// some other code
}
}
UI thread would not be blocked and I do not need to do anything. But that's the problem that it is some implementation detail I do not see directly because that could be in some nuget package. When I see that the UI freezes in some situations, I may find this problem (mean CPU-bound calculation before any await in async method) just after some investigation. There is no Wait() or Result, that would be easy to find, this is more problematic.
What I would like is to be prepared for that situation if possible in some simpler way. And that's why I do not want to use Task.Run whenewer I call some third-party library.
If I want to solve blocking problem, I could call the library in Task.Run like this:
It solves the problem, UI is not blocke anymore, but I've consumed one thread from ThreadPool. It is not good solution.
This is exactly what you want to do in a WinForms app. CPU-intensive code should be moved to a separate thread to free up the UI thread. There isn't any downside to consuming a new thread in WinForms.
Use Task.Run to move it to a different thread, and wait asynchronously from the UI thread for it to complete.
To quote the Asynchronous programming article from Microsoft:
If the work you have is CPU-bound and you care about responsiveness, use async and await, but spawn off the work on another thread with Task.Run.
I have no option to change the code
People say that, but you might not actually be hamstrung thus..
Here's a simple app with the same problem you face:
It's definitely pretty sleepy:
So let's whack it into ILSpy with the Reflexil plugin loaded:
We can perhaps shorten that timeout a bit.. Right click, Edit..
Make it 1ms, Right click the assembly and Save As..
That's a bit quicker!
Have a play, NOP it out etc..
You wrote:
If I want to solve blocking problem, I could call the library in Task.Run like this:
private static async Task CallLibraryAsync()
{
var library = new Library();
// added Task.Run
await Task.Run(() => library.DoSomethingAsync());
}
It solves the problem, UI is not blocked anymore, but I've consumed one thread from ThreadPool. It is not good solution.
(emphasis added)
...and then you proceed with inventing a convoluted hack that does the same thing: offloads the invocation of the DoSomethingAsync method to the ThreadPool. So you either want to:
Invoke the DoSomethingAsync method without using any thread at all, or
Invoke the DoSomethingAsync method on a non-ThreadPool thread.
The first is impossible. You can't invoke a method without using a thread. Code runs on CPUs, not on thin air. The second can be done in many ways, with the easiest being to use the Task.Factory.StartNew method, in combination with the LongRunning flag:
await Task.Factory.StartNew(() => library.DoSomethingAsync(), default,
TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
This way you will invoke the DoSomethingAsync on a newly created thread, which will be destroyed immediately after the invocation of the method has completed. To be clear, the thread will be destroyed when the invocation completes, not when the asynchronous operation completes. Based on the DoSomethingAsync implementation that you have included in the question (the first one), the invocation will complete immediately after creating the Task.Delay(1000) task, and initiating the await of this task. There will be nothing for the thread to do after this point, so it will be recycled.
Side notes:
The CallLibraryAsync method violates the guideline for not exposing asynchronous wrappers for synchronous methods. Since the DoSomethingAsync method is implemented as partially synchronous and partially asynchronous, the guideline still applies IMHO.
If you like the idea of controlling imperatively the current context, instead of controlling it with wrappers like the Task.Run method, you could check out this question: Why was SwitchTo removed from Async CTP / Release? There are (not very many) people who like it as well, and there are libraries available that make it possible (SwitchTo - Microsoft.VisualStudio.Threading).
When you use async/await for I/O or CPU-bound operations, your UI thread will not blocked. In your example, you use Thread.Sleep(2000);command for simulating your CPU-bound operations but this will block your thread-pool thread not UI thread. You can use Task.Delay(2000); for simulating your I/O operations without blocking thread-pool thread.

How to make ConcurrentExclusiveSchedulerPair work with async and await

I have following code:
static async Task Main()
{
ConcurrentExclusiveSchedulerPair concurrentExclusiveSchedulerPair = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, 4);
var factory = new TaskFactory(concurrentExclusiveSchedulerPair.ConcurrentScheduler);
for (int i = 0; i < 10; i++)
{
factory.StartNew(ThreadTask);
}
concurrentExclusiveSchedulerPair.Complete();
await concurrentExclusiveSchedulerPair.Completion;
Console.WriteLine("Completed");
}
private static async Task ThreadTask()
{
var random = new Random();
await Task.Delay(random.Next(100, 200));
Console.WriteLine($"Finished {Thread.CurrentThread.ManagedThreadId}");
}
and program finishes executing before tasks are completed. I understand why does it happens as ThreadTask returns completed task and from ConcurrentExclusiveSchedulerPair point of view it does finish executing. I also know few workarounds but is there any correct way to run this pattern with async?
The concept of the TaskScheduler was devised before the advent of the async/await, and it ended up not being compatible with it. You can see an experiment that demonstrates this incompatibility here: How to run a Task on a custom TaskScheduler using await?
The abstraction that is available for controlling the behavior of async/await is the SynchronizationContext. It is quite similar to a TaskScheduler. So much actually that some people have been wondering why we need both: What is the conceptual difference between SynchronizationContext and TaskScheduler.
If you are interested for something like a SingleThreadSynchronizationContext, you can find an implementation here: Await, SynchronizationContext, and Console Apps
TaskScheduler is compatible with async/await, but some of the behavior can be surprising.
When await captures its context, it captures the current SynchronizationContext unless it is null, in which case it captures the current TaskScheduler. So, ThreadTask will begin executing with the concurrent scheduler, and after its await it will resume on that same concurrent scheduler.
However, the semantics can be surprising, because the way async works with a task scheduler is that the async method is split into multiple tasks. Each await is a "split" point where the method is broken up. And only those smaller tasks are what is actually scheduled by the TaskScheduler.
So in your case, your code is starting 10 ThreadTask invocations, each running on the concurrent scheduler, and each of them hits an await point. Then the code calls Complete on the scheduler, which tells it not to accept any more tasks. Then when the awaits complete, they schedule the async method continuation (as a task) to that task scheduler, which as already been completed.
So, while technically await was designed to work with TaskScheduler, in practice few people use it that way. In particular, the "concurrent" and "exclusive" task schedulers can have surprising semantics, since a method that is suspended due to an await does not count as "running" to a task scheduler.

is using an an `async` lambda with `Task.Run()` redundant?

I just came across some code like:
var task = Task.Run(async () => { await Foo.StartAsync(); });
task.Wait();
(No, I don't know the inner-workings of Foo.StartAsync()). My initial reaction would be get rid of async/await and rewrite as:
var task = Foo.StartAsync();
task.Wait();
Would that be correct, or not (again, knowing nothing at all about Foo.StartAsync()). This answer to What difference does it make - running an 'async' action delegate with a Task.Run ... seems to indicate there may be cases when it might make sense, but it also says "To tell the truth, I haven't seen that many scenarios ..."
Normally, the intended usage for Task.Run is to execute CPU-bound code on a non-UI thread. As such, it would be quite rare for it to be used with an async delegate, but it is possible (e.g., for code that has both asynchronous and CPU-bound portions).
However, that's the intended usage. I think in your example:
var task = Task.Run(async () => { await Foo.StartAsync(); });
task.Wait();
It's far more likely that the original author is attempting to synchronously block on asynchronous code, and is (ab)using Task.Run to avoid deadlocks common in that situation (as I describe on my blog).
In essence, it looks like the "thread pool hack" that I describe in my article on brownfield asynchronous code.
The best solution is to not use Task.Run or Wait:
await Foo.StartAsync();
This will cause async to grow through your code base, which is the best approach, but may cause an unacceptable amount of work for your developers right now. This is presumably why your predecessor used Task.Run(..).Wait().
Mostly yes.
Using Task.Run like this is mostly used by people who don't understand how to execute an async method.
However, there is a difference. Using Task.Run means starting the async method on a ThreadPool thread.
This can be useful when the async method's synchronous part (the part before the first await) is substantial and the caller wants to make sure that method isn't blocking.
This can also be used to "get out of" the current context, for example where there isn't a SynchronizationContext.
It's worth noting that your method has to be marked async to be able to use the await keyword.
The code as written seems to be a workaround for running asynchronous code in a synchronous context. While I wouldn't say you should never ever do this, using async methods is preferable in almost every scenario.
// use this only when running Tasks in a synchronous method
// use async instead whenever possible
var task = Task.Run(async () => await Foo.StartAsync());
task.Wait();
Async methods, like your example of Foo.StartAsync(), should always return a Task object. This means that using Task.Run() to create another task is usually redundant in an async method. The task returned by the async method can simply be awaited by using the await keyword. The only reason you should use Task.Run() is when you are performing CPU-bound work that needs to be performed on a separate thread. The task returned by the async method can simply be awaited by using the await keyword. You can read more in depth details in Microsoft's guide to async programming.
In an async method, your code can be as simple as:
await Foo.StartAsync();
If you want to perform some other work while the task is running, you can assign the function to a variable and await the result (task completion) later.
For example:
var task = Foo.StartAsync();
// do some other work before waiting for task to finish
Bar();
Baz();
// now wait for the task to finish executing
await task;
With CPU-bound work that needs to be run on a separate thread, you can use Task.Run(), but you await the result instead of using the thread blocking Task.Wait():
var task = Task.Run(async () => await Foo.StartAsync());
// do some other work before waiting for task to finish
Bar();
Baz();
// now wait for the task to finish executing
await task;

When would I use Task.Yield()?

I'm using async/await and Task a lot but have never been using Task.Yield() and to be honest even with all the explanations I do not understand why I would need this method.
Can somebody give a good example where Yield() is required?
When you use async/await, there is no guarantee that the method you call when you do await FooAsync() will actually run asynchronously. The internal implementation is free to return using a completely synchronous path.
If you're making an API where it's critical that you don't block and you run some code asynchronously, and there's a chance that the called method will run synchronously (effectively blocking), using await Task.Yield() will force your method to be asynchronous, and return control at that point. The rest of the code will execute at a later time (at which point, it still may run synchronously) on the current context.
This can also be useful if you make an asynchronous method that requires some "long running" initialization, ie:
private async void button_Click(object sender, EventArgs e)
{
await Task.Yield(); // Make us async right away
var data = ExecuteFooOnUIThread(); // This will run on the UI thread at some point later
await UseDataAsync(data);
}
Without the Task.Yield() call, the method will execute synchronously all the way up to the first call to await.
Internally, await Task.Yield() simply queues the continuation on either the current synchronization context or on a random pool thread, if SynchronizationContext.Current is null.
It is efficiently implemented as custom awaiter. A less efficient code producing the identical effect might be as simple as this:
var tcs = new TaskCompletionSource<bool>();
var sc = SynchronizationContext.Current;
if (sc != null)
sc.Post(_ => tcs.SetResult(true), null);
else
ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(true));
await tcs.Task;
Task.Yield() can be used as a short-cut for some weird execution flow alterations. For example:
async Task DoDialogAsync()
{
var dialog = new Form();
Func<Task> showAsync = async () =>
{
await Task.Yield();
dialog.ShowDialog();
}
var dialogTask = showAsync();
await Task.Yield();
// now we're on the dialog's nested message loop started by dialog.ShowDialog
MessageBox.Show("The dialog is visible, click OK to close");
dialog.Close();
await dialogTask;
// we're back to the main message loop
}
That said, I can't think of any case where Task.Yield() cannot be replaced with Task.Factory.StartNew w/ proper task scheduler.
See also:
"await Task.Yield()" and its alternatives
Task.Yield - real usages?
One use of Task.Yield() is to prevent a stack overflow when doing async recursion. Task.Yield() prevents syncronous continuation. Note, however, that this can cause an OutOfMemory exception (as noted by Triynko). Endless recursion is still not safe and you're probably better off rewriting the recursion as a loop.
private static void Main()
{
RecursiveMethod().Wait();
}
private static async Task RecursiveMethod()
{
await Task.Delay(1);
//await Task.Yield(); // Uncomment this line to prevent stackoverlfow.
await RecursiveMethod();
}
Task.Yield() is like a counterpart of Thread.Yield() in async-await but with much more specific conditions. How many times do you even need Thread.Yield()? I will answer the title "when would you use Task.Yield()" broadly first. You would when the following conditions are fulfilled:
want to return the control to the async context (suggesting the task scheduler to execute other tasks in the queue first)
need to continue in the async context
prefer to continue immediately when the task scheduler is free
do not want to be cancelled
prefer shorter code
The term "async context" here means "SynchronizationContext first then TaskScheduler". It was used by Stephen Cleary.
Task.Yield() is approximately doing this (many posts get it slightly wrong here and there):
await Task.Factory.StartNew(
() => {},
CancellationToken.None,
TaskCreationOptions.PreferFairness,
SynchronizationContext.Current != null?
TaskScheduler.FromCurrentSynchronizationContext():
TaskScheduler.Current);
If any one of the conditions is broken, you need to use other alternatives instead.
If the continuation of a task should be in Task.DefaultScheduler, you normally use ConfigureAwait(false). On the contrary, Task.Yield() gives you an awaitable not having ConfigureAwait(bool). You need to use the approximated code with TaskScheduler.Default.
If Task.Yield() obstructs the queue, you need to restructure your code instead as explained by noseratio.
If you need the continuation to happen much later, say, in the order of millisecond, you would use Task.Delay.
If you want the task to be cancellable in the queue but do not want to check the cancellation token nor throw an exception yourself, you need to use the approximated code with a cancellation token.
Task.Yield() is so niche and easily dodged. I only have one imaginary example by mixing my experience. It is to solve an async dining philosopher problem constrained by a custom scheduler. In my multi-thread helper library InSync, it supports unordered acquisitions of async locks. It enqueues an async acquisition if the current one failed. The code is here. It needs ConfigureAwait(false) as a general purpose library so I need to use Task.Factory.StartNew. In a closed source project, my program needs to execute significant synchronous code mixed with async code with
a high thread priority for semi-realtime work
a low thread priority for some background work
a normal thread priority for UI
Thus, I need a custom scheduler. I could easily imagine some poor developers somehow need to mix sync and async code together with some special schedulers in a parallel universe (one universe probably does not contain such developers); but why wouldn't they just use the more robust approximated code so they do not need to write a lengthy comment to explain why and what it does?
Task.Yield() may be used in mock implementations of async methods.

Why is TaskScheduler.Current the default TaskScheduler?

The Task Parallel Library is great and I've used it a lot in the past months. However, there's something really bothering me: the fact that TaskScheduler.Current is the default task scheduler, not TaskScheduler.Default. This is absolutely not obvious at first glance in the documentation nor samples.
Current can lead to subtle bugs since its behavior is changing depending on whether you're inside another task. Which can't be determined easily.
Suppose I am writting a library of asynchronous methods, using the standard async pattern based on events to signal completion on the original synchronisation context, in the exact same way XxxAsync methods do in the .NET Framework (eg DownloadFileAsync). I decide to use the Task Parallel Library for implementation because it's really easy to implement this behavior with the following code:
public class MyLibrary
{
public event EventHandler SomeOperationCompleted;
private void OnSomeOperationCompleted()
{
SomeOperationCompleted?.Invoke(this, EventArgs.Empty);
}
public void DoSomeOperationAsync()
{
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000); // simulate a long operation
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
.ContinueWith(t =>
{
OnSomeOperationCompleted(); // trigger the event
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
So far, everything works well. Now, let's make a call to this library on a button click in a WPF or WinForms application:
private void Button_OnClick(object sender, EventArgs args)
{
var myLibrary = new MyLibrary();
myLibrary.SomeOperationCompleted += (s, e) => DoSomethingElse();
myLibrary.DoSomeOperationAsync(); // call that triggers the event asynchronously
}
private void DoSomethingElse() // the event handler
{
//...
Task.Factory.StartNew(() => Thread.Sleep(5000)); // simulate a long operation
//...
}
Here, the person writing the library call chose to start a new Task when the operation completes. Nothing unusual. He or she follows examples found everywhere on the web and simply use Task.Factory.StartNew without specifying the TaskScheduler (and there is no easy overload to specify it at the second parameter). The DoSomethingElse method works fine when called alone, but as soon at it's invoked by the event, the UI freezes since TaskFactory.Current will reuse the synchronization context task scheduler from my library continuation.
Finding out this could take some time, especially if the second task call is buried down in some complex call stack. Of course, the fix here is simple once you know how everything works: always specify TaskScheduler.Default for any operation you're expecting to be running on the thread pool. However, maybe the second task is started by another external library, not knowing about this behavior and naively using StartNew without a specific scheduler. I'm expecting this case to be quite common.
After wrapping my head around it, I can't understand the choice of the team writing the TPL to use TaskScheduler.Current instead of TaskScheduler.Default as the default:
It's not obvious at all, Default is not the default! And the documentation is seriously lacking.
The real task scheduler used by Current depends of the call stack! It's hard to maintain invariants with this behavior.
It's cumbersome to specify the task scheduler with StartNew since you have to specify the task creation options and cancellation token first, leading to long, less readable lines. This can be alleviated by writing an extension method or creating a TaskFactory that uses Default.
Capturing the call stack has additional performance costs.
When I really want a task to be dependent on another parent running task, I prefer to specify it explicitly to ease code reading rather than rely on call stack magic.
I know this question may sound quite subjective, but I can't find a good objective argument as to why this behavior is as it. I'm sure I'm missing something here: that's why I'm turning to you.
I think the current behavior makes sense. If I create my own task scheduler, and start some task that starts other tasks, I probably want all the tasks to use the scheduler I created.
I agree that it's odd that sometimes starting a task from the UI thread uses the default scheduler and sometimes not. But I don't know how would I make this better if I was designing it.
Regarding your specific problems:
I think the easiest way to start a new task on a specified scheduler is new Task(lambda).Start(scheduler). This has the disadvantage that you have to specify type argument if the task returns something. TaskFactory.Create can infer the type for you.
You can use Dispatcher.Invoke() instead of using TaskScheduler.FromCurrentSynchronizationContext().
[EDIT]
The following only addresses the problem with the scheduler used by Task.Factory.StartNew.
However, Task.ContinueWith has a hardcoded TaskScheduler.Current.
[/EDIT]
First, there is an easy solution available - see the bottom of this post.
The reason behind this problem is simple: There is not only a default task scheduler (TaskScheduler.Default) but also a default task scheduler for a TaskFactory (TaskFactory.Scheduler).
This default scheduler can be specified in the constructor of the TaskFactory when it's created.
However, the TaskFactory behind Task.Factory is created as follows:
s_factory = new TaskFactory();
As you can see, no TaskScheduler is specified; null is used for the default constructor - better would be TaskScheduler.Default (the documentation states that "Current" is used which has the same consequences).
This again leads to the implementation of TaskFactory.DefaultScheduler (a private member):
private TaskScheduler DefaultScheduler
{
get
{
if (m_defaultScheduler == null) return TaskScheduler.Current;
else return m_defaultScheduler;
}
}
Here you should see be able to recognize the reason for this behaviour: As Task.Factory has no default task scheduler, the current one will be used.
So why don't we run into NullReferenceExceptions then, when no Task is currently executing (i.e. we have no current TaskScheduler)?
The reason is simple:
public static TaskScheduler Current
{
get
{
Task internalCurrent = Task.InternalCurrent;
if (internalCurrent != null)
{
return internalCurrent.ExecutingTaskScheduler;
}
return Default;
}
}
TaskScheduler.Current defaults to TaskScheduler.Default.
I would call this a very unfortunate implementation.
However, there is an easy fix available: We can simply set the default TaskScheduler of Task.Factory to TaskScheduler.Default
TaskFactory factory = Task.Factory;
factory.GetType().InvokeMember("m_defaultScheduler", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly, null, factory, new object[] { TaskScheduler.Default });
I hope I could help with my response although it's quite late :-)
Instead of Task.Factory.StartNew()
consider using: Task.Run()
This will always execute on a thread pool thread. I just had the same problem described in the question and I think that is a good way of handling this.
See this blog entry:
http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
It's not obvious at all, Default is not the default! And the documentation is seriously lacking.
Default is the default, but it's not always the Current.
As others have already answered, if you want a task to run on the thread pool, you need to explicitly set the Current scheduler by passing the Default scheduler into either the TaskFactory or the StartNew method.
Since your question involved a library though, I think the answer is that you should not do anything that will change the Current scheduler that's seen by code outside your library. That means that you should not use TaskScheduler.FromCurrentSynchronizationContext() when you raise the SomeOperationCompleted event. Instead, do something like this:
public void DoSomeOperationAsync() {
var context = SynchronizationContext.Current;
Task.Factory
.StartNew(() => Thread.Sleep(1000) /* simulate a long operation */)
.ContinueWith(t => {
context.Post(_ => OnSomeOperationCompleted(), null);
});
}
I don't even think you need to explicitly start your task on the Default scheduler - let the caller determine the Current scheduler if they want to.
I've just spent hours trying to debug a weird issue where my task was scheduled on the UI thread, even though I didn't specify it to. It turned out the problem was exactly what your sample code demonstrated: A task continuation was scheduled on the UI thread, and somewhere in that continuation, a new task was started which then got scheduled on the UI thread, because the currently executing task had a specific TaskScheduler set.
Luckily, it's all code I own, so I can fix it by making sure my code specify TaskScheduler.Default when starting new tasks, but if you aren't so lucky, my suggestion would be to use Dispatcher.BeginInvoke instead of using the UI scheduler.
So, instead of:
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() => Thread.Sleep(5000));
task.ContinueWith((t) => UpdateUI(), uiScheduler);
Try:
var uiDispatcher = Dispatcher.CurrentDispatcher;
var task = Task.Factory.StartNew(() => Thread.Sleep(5000));
task.ContinueWith((t) => uiDispatcher.BeginInvoke(new Action(() => UpdateUI())));
It's a bit less readable though.

Categories

Resources