The correct way to offload concurrent work to different threads is to use Task.Run
as rossipedia suggested.
The best solutions for background processing in ASP.Net (where your AppDomain
can be recycled/shut down automatically together with all your tasks) are in Scott Hanselman and Stephen Cleary's blogs (e.g. HangFire)
However, you could utilize Task.Yield
together with ConfigureAwait(false)
to achieve the same.
All Task.Yield
does is return an awaiter that makes sure the rest of the method doesn't proceed synchronously (by having IsCompleted
return false
and OnCompleted
execute the Action
parameter immediately). ConfigureAwait(false)
disregards the SynchronizationContext
and so forces the rest of the method to execute on a ThreadPool
thread.
If you use both together you can make sure an async
method returns a task immediately which will execute on a ThreadPool
thread (like Task.Run
):
async Task CreateFileFromLongRunningComputation(int input)
{
await Task.Yield().ConfigureAwait(false);
// executed on a ThreadPool thread
}
Edit:
George Mauer pointed out that since Task.Yield
returns YieldAwaitable
you can't use ConfigureAwait(false)
which is a method on the Task
class.
You can achieve something similar by using Task.Delay
with a very short timeout, so it wouldn't be synchronous but you wouldn't waste much time:
async Task CreateFileFromLongRunningComputation(int input)
{
await Task.Delay(1).ConfigureAwait(false);
// executed on a ThreadPool thread
}
A better option would be to create a YieldAwaitable
that simply disregards the SynchronizationContext
the same as using ConfigureAwait(false)
does:
async Task CreateFileFromLongRunningComputation(int input)
{
await new NoContextYieldAwaitable();
// executed on a ThreadPool thread
}
public struct NoContextYieldAwaitable
{
public NoContextYieldAwaiter GetAwaiter() { return new NoContextYieldAwaiter(); }
public struct NoContextYieldAwaiter : INotifyCompletion
{
public bool IsCompleted { get { return false; } }
public void OnCompleted(Action continuation)
{
var scheduler = TaskScheduler.Current;
if (scheduler == TaskScheduler.Default)
{
ThreadPool.QueueUserWorkItem(RunAction, continuation);
}
else
{
Task.Factory.StartNew(continuation, CancellationToken.None, TaskCreationOptions.PreferFairness, scheduler);
}
}
public void GetResult() { }
private static void RunAction(object state) { ((Action)state)(); }
}
}
This isn't a recommendation, it's an answer to your Task.Yield questions.