Der Mangel an nicht erfassendem Task.Yield zwingt mich, Task.Run zu verwenden. Warum sollte ich dem folgen?

Entschuldigung im Voraus, wenn diese Frage meinungsbasiert ist. Der Mangel anTask.Yield Version, die den Ausführungskontext nicht erfassen würde, wurde bereits besprochenHier. Anscheinend war diese Funktion in früheren Versionen von Async CTP in irgendeiner Form vorhanden, wurde jedoch entfernt, da sie leicht missbraucht werden konnte.

IMO, könnte eine solche Funktion so leicht missbraucht werden wieTask.Run selbst. Hier ist was ich meine. Stellen Sie sich vor, es gibt eine erwartbareSwitchContext.Yield API, die die Fortsetzung auf ThreadPool plant, sodass die Ausführung immer auf einem anderen Thread als dem aufrufenden Thread fortgesetzt wird. Ich hätte es im folgenden Code verwenden können, der einige CPU-gebundene Arbeit von einem UI-Thread startet. Ich würde es als eine bequeme Möglichkeit betrachten, die CPU-gebundene Arbeit an einem Pool-Thread fortzusetzen:

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

}

Jetzt ohneSwitchContext.Yield, DoWorkAsync würde wie folgt aussehen. Es fügt eine zusätzliche Komplexitätsebene in Form von asynchroner Delegierung und Aufgabenverschachtelung hinzu:

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

Das heißt, die UmsetzungSwitchContext.Yield kann eigentlich ganz einfach und (ich wage zu sagen) effizient sein:

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() { }
    }
}

So,meine Frage istWarum sollte ich die zweite Version von bevorzugen?DoWorkAsync über die erste, und warum würde mitSwitchContext.Yield als schlechte Praxis angesehen werden?

Antworten auf die Frage(1)

Ihre Antwort auf die Frage