Отсутствие не захвата Task.Yield заставляет меня использовать Task.Run, зачем это нужно?
Заранее извиняюсь, если этот вопрос основан на мнении. ОтсутствиеTask.Yield версия, которая не будет захватывать контекст выполнения, уже обсуждаласьВот. По-видимому, эта функция присутствовала в той или иной форме в ранних версиях Async CTP, но была удалена, поскольку ее можно было легко использовать неправильно.
ИМО, такая функция может быть использована так же легко, какTask.Run
сам. Вот что я имею в виду. Представьте, что есть ожиданиеSwitchContext.Yield
API, который планирует продолжение в ThreadPool, поэтому выполнение всегда будет продолжаться в потоке, отличном от вызывающего потока. Я мог бы использовать его в следующем коде, который начинает некоторую работу с процессором из потока пользовательского интерфейса. Я считаю, что это удобный способ продолжить работу с процессором в потоке пула:
class Worker
{
static void Log(string format, params object[] args)
{
Debug.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, String.Format(format, args));
}
public async Task UIAction()
{
// UI Thread
Log("UIAction");
// start the CPU-bound work
var cts = new CancellationTokenSource(5000);
var workTask = DoWorkAsync(cts.Token);
// possibly await for some IO-bound work
await Task.Delay(1000);
Log("after Task.Delay");
// finally, get the result of the CPU-bound work
int c = await workTask;
Log("Result: {0}", c);
}
async Task<int> DoWorkAsync(CancellationToken ct)
{
// start on the UI thread
Log("DoWorkAsync");
// switch to a pool thread and yield back to the UI thread
await SwitchContext.Yield();
Log("after SwitchContext.Yield");
// continue on a pool thread
int c = 0;
while (!ct.IsCancellationRequested)
{
// do some CPU-bound work on a pool thread: counting cycles :)
c++;
// and use async/await too
await Task.Delay(50);
}
return c;
}
}
Сейчас безSwitchContext.Yield
, DoWorkAsync
будет выглядеть ниже. Это добавляет дополнительный уровень сложности в виде асинхронного делегирования и вложения задач:
async Task<int> DoWorkAsync(CancellationToken ct)
{
// start on the UI thread
Log("DoWorkAsync");
// Have to use async delegate
// Task.Run uwraps the inner Task<int> task
return await Task.Run(async () =>
{
// continue on a pool thread
Log("after Task.Yield");
int c = 0;
while (!ct.IsCancellationRequested)
{
// do some CPU-bound work on a pool thread: counting cycles :)
c++;
// and use async/await too
await Task.Delay(50);
}
return c;
});
}
Тем не менее, реализацияSwitchContext.Yield
на самом деле может быть довольно простым и (осмелюсь сказать) эффективным:
public static class SwitchContext
{
public static Awaiter Yield() { return new Awaiter(); }
public struct Awaiter : System.Runtime.CompilerServices.INotifyCompletion
{
public Awaiter GetAwaiter() { return this; }
public bool IsCompleted { get { return false; } }
public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem((state) => ((Action)state)(), continuation);
}
public void GetResult() { }
}
}
Так,мой вопроспочему я предпочитаю вторую версиюDoWorkAsync
над первым, и зачем использоватьSwitchContext.Yield
считаться плохой практикой?