Given a SynchronizationContext
, which I already have (and is basically a window to a specific thread), how do I create Task
s that are posted to this context?
For reference, here's a very basic demonstration of how the SynchronizationContext
is set up.
public class SomeDispatcher : SynchronizationContext
{
SomeDispatcher() {
new Thread(() => {
SynchronizationContext.SetSynchronizationContext(this);
// Dispatching loop (among other things)
}).Start();
}
override void Post(SendOrPostCallback d, object state)
{
// Add (d, state) to a dispatch queue;
}
}
This works fine for async / awaits that are already running in the context.
Now, I want to be able to post Task
s to this from an outside context (e.g. from a UI thread) but can't seem to find a clean way of doing this.
One way to do this is by using TaskCompletionSource<>
.
Task StartTask(Action action)
{
var tcs = new TaskCompletionSource<object>();
SaidDispatcher.Post(state => {
try
{
action.Invoke();
tcs.SetResult(null);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
return tcs.Task;
});
But this is reinventing the wheel and a major pain supporting variations such as StartNew(Func<TResult>)
, StartNew(Func<Task<TResult>>)
, etc.
A TaskFactory
interface to the SynchronizationContext
is probably ideally, but I can't seem to instantiate one cleanly:
TaskFactory CreateTaskFactory()
{
var original = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(SomeDispatcher); // yuck!
try
{
return new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
}
finally
{
SynchronizationContext.SetSynchronizationContext(original);
}
}
(i.e. Having to temporary hose the current thread's synchronization context seems hacky.)
It seems default SynchronizationContextTaskScheduler
is
But it's source code is available here and we see it's relatively simple, so we can try to roll out our own scheduler, like this:
public sealed class MySynchronizationContextTaskScheduler : TaskScheduler {
private readonly SynchronizationContext _synchronizationContext;
public MySynchronizationContextTaskScheduler(SynchronizationContext context) {
_synchronizationContext = context;
}
[SecurityCritical]
protected override void QueueTask(Task task) {
_synchronizationContext.Post(PostCallback, task);
}
[SecurityCritical]
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) {
if (SynchronizationContext.Current == _synchronizationContext) {
return TryExecuteTask(task);
}
else
return false;
}
[SecurityCritical]
protected override IEnumerable<Task> GetScheduledTasks() {
return null;
}
public override Int32 MaximumConcurrencyLevel
{
get { return 1; }
}
private void PostCallback(object obj) {
Task task = (Task) obj;
base.TryExecuteTask(task);
}
}
Then your CreateTaskFactory
becomes:
TaskFactory CreateTaskFactory() {
return new TaskFactory(new MySynchronizationContextTaskScheduler(SomeDispatcher));
}
And you create tasks with:
var factory = CreateTaskFactory();
var task = factory.StartNew(...);
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