Отсутствие не захвата 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 считаться плохой практикой?

Ответы на вопрос(1)

Ваш ответ на вопрос