缺少非捕获Task.Yield迫使我使用Task.Run,​​为什么要这样做呢?

nos*_*tio 3 .net c# multithreading task-parallel-library async-await

如果这个问题是基于意见的,请提前道歉.这里已经讨论了缺少不捕获执行上下文的Task.Yield版本.显然,这个功能在早期版本的Async CTP中以某种形式出现,但由于它很容易被滥用而被删除.

IMO,这样的功能可能很容易被滥用Task.Run.这就是我的意思.想象一下,有一个等待的SwitchContext.YieldAPI可以调度ThreadPool上的延续,因此执行将始终在与调用线程不同的线程上继续.我可以在下面的代码中使用它,它从UI线程启动一些CPU绑定的工作.我认为这是一种在池线程上继续CPU绑定工作的便捷方式:

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;
    }

}
Run Code Online (Sandbox Code Playgroud)

现在,没有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;
    });
}
Run Code Online (Sandbox Code Playgroud)

也就是说,实施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() { }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,我的问题是,为什么我更喜欢DoWorkAsync第一个版本而不是第一个版本,为什么使用SwitchContext.Yield被认为是一种不好的做法?

Ste*_*ary 5

你不具备Task.RunDoWorkAsync.考虑这个选项:

public async Task UIAction()
{
    // UI Thread
    Log("UIAction");

    // start the CPU-bound work
    var cts = new CancellationTokenSource(5000);
    var workTask = Task.Run(() => 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);
}
Run Code Online (Sandbox Code Playgroud)

这导致代码具有更清晰的意图.DoWorkAsync是一种自然同步的方法,因此它具有同步签名.DoWorkAsync既不知道也不关心用户界面.的UIAction,这不关心UI线程,工作推到关闭使用后台线程Task.Run.

作为一般规则,尽可能尝试Task.Run从库方法中"推送"任何调用.

  • 实际上,在同一方法*中混合使用"async"和CPU密集型代码是很少见的.但是在这种情况下,您可以将`DoWorkAsync`留给`async`签名,并在我的回答中仍以相同的方式调用它. (4认同)