Task.RunSynchronously 有啥用?

Posted

技术标签:

【中文标题】Task.RunSynchronously 有啥用?【英文标题】:What is Task.RunSynchronously for?Task.RunSynchronously 有什么用? 【发布时间】:2019-03-16 12:13:29 【问题描述】:

我只是想知道方法是什么?在什么样的场景下我可以使用这种方法。

我最初的想法是 RunSynchronously 用于调用异步方法并同步运行该方法,而不会像 .wait() 那样导致死锁问题。

但是,根据MSDN,

通常,任务在线程池线程上异步执行,不会阻塞调用线程。通过调用 RunSynchronously() 方法执行的任务与当前的 TaskScheduler 相关联,并在调用线程上运行。如果目标调度器不支持在调用线程上运行该任务,则该任务将按计划调度执行,调用线程将阻塞直到任务执行完毕

如果任务要在调用线程上运行,为什么这里需要一个 TaskScheduler?

【问题讨论】:

看起来它会让调度程序决定何时运行它,而不是根据您复制的描述立即在当前线程上运行它。调度器会在这里做出决定,而不是立即同步运行。 它对大多数异步方法没有任何用处,因为它们返回“热”任务(即已经运行)您只能将 RunRunSynchronously 与“冷”任务一起使用还没开始。 Stephen Cleary blog 似乎已经涵盖了它。顶部的免责声明很有趣 在这篇博文中绝对没有推荐用于现代代码的内容。如果您正在寻找最佳实践,请继续前进;这里没什么可看的。所以它似乎是遗产 方法 RunSynchronously 是在 .NET 4.5 中引入 async await 流之前创建的,显然这个想法是允许同步执行“冷”任务并尝试使用相同的线程上下文,并且指定的调度程序。与.StartNew() 相同,它已过时。除非你需要创建一个冷任务并在当前线程中同步运行它,使用当前的TaskScheduler,否则使用它是没有意义的。 @Fabjan 两者都有一个小案例,因为它们接受 TaskScheduler 参数。如果您想使用自定义调度程序,例如,limed DOP 调度程序。即使这样,也有更好的选择,例如具有有限 DOP 的 ActionBlock 【参考方案1】:

RunSynchronously 将何时启动任务的决定委托给当前任务调度程序(或作为参数传递的调度程序)。

我不确定它为什么会存在(可能是内部使用或遗留使用),但在当前版本的 .NET 中很难想出一个有用的用例。 @Fabjan has a possible explanation in his comment 提问。

RunSynchronously 要求调度程序同步运行它,但调度程序很可能会忽略提示并在线程池线程中运行它,您当前的线程将同步阻塞,直到它完成。

调度程序不必在当前线程上运行它,也不必立即运行它,尽管我认为这是常见调度程序(ThreadPoolTask​​Scheduler 和常见 UI 调度程序)上会发生的事情。

RunSynchronously 也会在任务已经启动或完成/出错时抛出异常(这意味着您将无法在异步方法上使用它)。

此代码可能会阐明不同的行为:

WaitResult 根本不运行任务,它们只是等待当前线程上的任务完成并阻塞它直到完成,所以如果我们想比较,我们可以比较 StartWaitRunSynchronously

class Scheduler : TaskScheduler

    protected override void QueueTask(Task task) => 
        Console.WriteLine("QueueTask");

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    
        Console.WriteLine("TryExecuteTaskInline");

        return false;
    

    protected override IEnumerable<Task> GetScheduledTasks() => throw new NotImplementedException();


static class Program

    static void Main()
    
        var taskToStart = new Task(() =>  );
        var taskToRunSynchronously = new Task(() =>  );

        taskToStart.Start(new Scheduler());
        taskToRunSynchronously.RunSynchronously(new Scheduler());
    

如果您尝试注释 Start 或 RunSynchronously 并运行代码,您将看到 Start 尝试将任务排队到调度程序,而 RunSynchronously 将尝试内联执行它,如果失败(返回 false) ,它只会排队。

【讨论】:

【参考方案2】:

首先让我们看一下这段代码:

public async static Task<int> MyAsyncMethod()

   await Task.Delay(100);
   return 100;


//Task.Delay(5000).RunSynchronously();                        // bang
//Task.Run(() => Thread.Sleep(5000)).RunSynchronously();     // bang
// MyAsyncMethod().RunSynchronously();                      // bang

var t = new Task(() => Thread.Sleep(5000));
t.RunSynchronously();                                     // works

在此示例中,我们尝试在以下任务上调用 RunSynchronously

返回其他带有结果的任务(承诺任务) 在线程池线程上运行的“热”委托任务 async await 创建的另一个承诺任务 与代表的“冷”任务

创建后会有什么状态?

等待激活 等待运行 等待激活 已创建

所有 'hot' 和 promise 任务都是以WaitingForActivationWaitingToRun 状态创建的。热门任务也与任务调度程序相关联。

方法RunSynchronously 只知道如何处理包含委托且状态为Created 的“冷”任务。

结论:

方法RunSynchronously 可能出现在没有“热”任务或者它们没有被广泛使用并且是为特定目的而创建的时候。

我们可能希望在需要自定义TaskScheduler 的“冷”任务时使用它,否则它已经过时且毫无用处。

为了同步运行“热”任务(我们应该尽量避免),我们可以使用task.GetAwaiter().GetResult()。这与.Result 相同,但作为奖励,它返回原始异常而不是AggregateException。尽管如此,“同步优于异步”并不是最佳选择,应尽可能避免。

【讨论】:

可能值得注意的是,Thread(基本上)已被Task (Task.Delay()) 取代,因此Thread.Sleep 现在也可以被视为旧版。这就是它与旧版 RunSynchronously 一起使用的原因 GetAwaiter:此方法旨在供编译器使用,而不是在应用程序代码中使用。 docs.microsoft.com/en-us/dotnet/api/… @Ares:请参阅this answer,了解 task.Result 与 task.GetAwaiter().GetResult()。

以上是关于Task.RunSynchronously 有啥用?的主要内容,如果未能解决你的问题,请参考以下文章

DataContext 有啥用?

UnmanagedMemoryStream 有啥用?

appStoreReceiptURL 有啥用?

有啥用?和:[重复]

XlaBuilder 有啥用?

构造函数有啥用? [关闭]