为什么没有Task.Run重载接受任务?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么没有Task.Run重载接受任务?相关的知识,希望对你有一定的参考价值。

我发现自己反复这样做:

var task = f(); // some code returning a task

Task.Run(async () => await task);

对我来说,这是我经常迷惑的代码。是否有原因为什么 Task类没有接受任务的重载?

public static Task Run(Task task) => Task.Run(async () => await task);

答案

为什么Task类没有接受任务的Run重载?

是,因为不应该有一个。

为了这个答案,TaskTask<T>表示执行某些工作并返回可能存在或可能不存在的值的某些操作。它是对各种工作的抽象(例如,在另一个线程上运行的并发操作,在硬件中其他地方运行的异步IO操作,同步操作的表示或其他形式。

Task / Task<T>代表什么[[不是是Func<>Action<>,也不代表可以用来启动新操作的“作业模板”(将其视为代表已经开始的工作)。

特别是对于Task.Run:在.NET中,真正的Task.Run(Func<>) / Task.Run(Action)方法是默认调度程序的线程池中的Func<>Action<>的简写形式(即,并发,多-线程)。您无法“重新启动” TaskTask的状态机图严格是单向的),只能使用用于启动原始Task的任何机制来启动新的Task。因此,例如,您不能任意重启异步Socket操作,因为这将意味着倒退整个程序的状态,并且这意味着违反物理定律...

如果您有一个Task<T>对象,那么(假设您使用的是正确的)对象所代表的任何操作将已经被调度或以其他方式启动-或已经完成-因此,您不能通过以下方式“启动” Task将其传递给Task.Run,因为它已经启动了(这过于简化)。

您提供的示例(在下面重新发布)没有任何用处:


public static Task Run(Task task) => Task.Run(async () => await task);

为了方便阅读,我在下面用长篇文章重写了它:

public static Task Run(Task originalTask)

    LambdaCapture capture = new LambdaCapture( originalTask );
    Task poolTask = Run( capture.Run ); // Remember that a Delegate includes the `this` reference unlike a raw C-style function-pointer.
    return poolTask;


// Oversimplified representation of what Task.Run does:
public static Task Run( Action action )

    ThreadPool pool = GetThreadPoolFromSomewhere();

    TaskCompletionSource tsc = new TaskCompletionSource();

    Action wrappedAction = () =>
    
        // Run the action:
        action();

        // When it completes, inform TaskCompletionSource:
        tsc.SetResult(); // Task (not `Task<T>`) has no result value.

        // When `SetResult()` is invoked, the thread running this code will not return to here until after it runs the contination scheduled after `originalTask`.
    ;

    pool.AddJob( wrappedAction ); // Adds `wrappedAction` to a queue which is dequeued by the first available thread.

    return tsc.Task; // <-- this is a new Task created by the TaskCompletionSource.


private class LambdaCapture

    private readonly Task originalTask;

    public Runnable( Task originalTask )
    
        this.originalTask = originalTask;
    

    public async Task Run()
    
         await this.originalTask;
    

当您建议的Task.Run(Task)方法被调用时,它会执行此操作:

它将安排LambdaCapture.Run在线程池中的第一个可用线程上运行。

    然后它将创建并返回一个单独的新Task实例,以表示线程池操作(即并发操作),而不考虑originalTask的性质。
  1. [当线程池可用并且工作线程运行LambdaCapture.Run时,它将
  2. (过度简化警告)
  3. 检查originalTask是否完成,如果完成,将返回并通知originalTask' s完成的调度程序,如果没有完成,它将调度Runnable.Run的其余部分(即await之后的所有代码-在这种情况下只是一个return;语句)在将其设为
  4. continuation。因此,当originalTask确实完成(假设它[[does
  5. 完成)时,从originalTask分配下一个延续的线程将运行originalTask操作的其余部分,并通知工作线程池调度程序完成,然后(大概)从Task.Run返回的任何代码awaitawaits执行Task的延续。
  6. 如果这令人困惑,那是因为我很想解释。 C#中的Task.Runjavascript / TypeScript中的Task<T>或C ++中的Promise<T>基本相同。
  7. 简而言之:除了在线程池线程中浪费CPU周期外,没有任何理由要做您想做的事情。如@Fabio所说,只需使用原始方法执行std::promise
    如果您的原始方法不是await task,因为该方法不是await task方法,那么即使async存在,它也无济于事,因为您[[still需要Task.Run(Task) await返回的Task

以上是关于为什么没有Task.Run重载接受任务?的主要内容,如果未能解决你的问题,请参考以下文章

为啥对象没有接受 IFormatProvider 的重载?

使用异步方法等待 Task.Run 不会在正确的线程上引发异常

Parallel.ForEach 与 Task.Run 和 Task.WhenAll

避免 Task.Run 导致主线程死锁(C#)

在同步方法中运行没有 Wait() 的 Task.Run() 有啥影响?

task 异步