Is it possible to force a task to execute synchronously, on the current thread?
That is, is it possible, by e.g. passing some parameter to StartNew()
, to ma
OP here. This is my final solution (which actually solves a lot more than I asked about).
I use the same implementation for Threads
in both test and production, but pass in different TaskSchedulers
:
public class Threads
{
private readonly TaskScheduler _executeScheduler;
private readonly TaskScheduler _continueScheduler;
public Threads(TaskScheduler executeScheduler, TaskScheduler continueScheduler)
{
_executeScheduler = executeScheduler;
_continueScheduler = continueScheduler;
}
public TaskContinuation StartNew(Func func)
{
var task = Task.Factory.StartNew(func, CancellationToken.None, TaskCreationOptions.None, _executeScheduler);
return new TaskContinuation(task, _continueScheduler);
}
}
I wrap the Task
in a TaskContinuation
class in order to be able to specify TaskScheduler
for the ContinueWith()
call.
public class TaskContinuation
{
private readonly Task _task;
private readonly TaskScheduler _scheduler;
public TaskContinuation(Task task, TaskScheduler scheduler)
{
_task = task;
_scheduler = scheduler;
}
public void ContinueWith(Action> func)
{
_task.ContinueWith(func, _scheduler);
}
}
I create my custom TaskScheduler
that dispatches the action on the thread that scheduler was created on:
public class CurrentThreadScheduler : TaskScheduler
{
private readonly Dispatcher _dispatcher;
public CurrentThreadScheduler()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
protected override void QueueTask(Task task)
{
_dispatcher.BeginInvoke(new Func(() => TryExecuteTask(task)));
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return true;
}
protected override IEnumerable GetScheduledTasks()
{
return Enumerable.Empty();
}
}
Now I can specify the behaviour by passing in different TaskSchedulers
to the Threads
constructor.
new Threads(TaskScheduler.Default, TaskScheduler.FromCurrentSynchronizationContext()); // Production
new Threads(TaskScheduler.Default, new CurrentThreadScheduler()); // Let the tests use background threads
new Threads(new CurrentThreadScheduler(), new CurrentThreadScheduler()); // No threads, all synchronous
Finally, since the event loop doesn't run automatically in my unit test, I have to execute it manually. Whenever I need to wait for a background operation to complete I execute the following (from the main thread):
DispatcherHelper.DoEvents();
The DispatcherHelper
can be found here.