Why is TaskScheduler.Current the default TaskScheduler?
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 usingTaskScheduler.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 :-)