What is the lifetime of SetSynchronizationContext?

174 Views Asked by At

I have a custom SynchronizationContext which abstracts away a custom work queue system. I'm trying to write a helper function which makes it easy for client code to write code for it.

My helper function currently looks like:

void async Task QueueTaskWithCustomContext(Func<Task> task) {
  var oldContext = SynchronizationContext.Current;
  try {
    SynchronizationContext.SetCurrent(new CustomSynchronizationContext());
    // Yielding prevents any inline synchronous work from being done outside the work queue.
    await Task.Yield();
    await task();
  } finally {
    SynchronizationContext.SetCurrent(oldContext);
  }
}

And I expect to be used like:

await QueueTaskWithCustomContext(async() => {
  await Something();
  await SomethingElse();
});

I don't know if this is kosher but it seems to work for the test cases I've thrown at it. I'm a little nervous about how I'm setting the Synchronization context and how it might interact with asynchronous code, though. Specifically, are there any situations where the custom synchronization context could "leak" out of the helper function and be set for other tasks? I'm thinking if the helper function isn't awaited immediately. I couldn't make it happen after some quick experimentation but I'm still nervous.

Alternatively, is the way I've set up the try/finally block guaranteed to set the synchronization context even after the first await? Again, I couldn't make it happen from some quick experimentation but I'm still nervous about it.

I suppose I don't really understand the lifetime of setting the SynchronizationContext. Is it set forever, or until unset, or just for the current function or...?

1

There are 1 best solutions below

2
Blindy On

Specifically, are there any situations where the custom synchronization context could "leak" out of the helper function and be set for other tasks?

Absolutely:

QueueTaskWithCustomContext(async() => await Task.Delay(1000));
await Task.Delay(10);

The smaller delay outside will run on your custom scheduler, not the default scheduler.

I wholeheartedly recommend against this kind of degenerate code, you're inviting nothing but trouble by surprising your users with something as basic as the task scheduler suddenly and unexpectedly changing.

Edit: Since I didn't explicitly answer your main question, the life time of the "current" task scheduler is the lifetime of the thread. It's stored in TLS so it will exist as long as your thread exists.