在C#中,Foo().Result和Task.Run(() => Foo()).Result有什么区别?

8

4
两种做法都可能是错误的。如果MyMethod返回一个Task<T>,那么第一种方法会导致死锁,而第二种方法仅将Task<T>分配给r,而没有获取该Task的结果(在这种情况下,Task.Run最终创建了一个Task<Task<T>>)。 - Damien_The_Unbeliever
我这里有更多的上下文:https://github.com/webCRMdotcom/erp-integrations/pull/92/commits/dd8af89899ce1de837ef6e34f0688a685a5cea3b。就像@Chrᴉz所提到的那样,我不需要两次写`.Result`。 - Jan Aagaard
@JanAagaard 您是对的。我的错。 - Chrᴉz remembers Monica
@Damien_The_Unbeliever:r 被赋值为类型 T,而不是 Task<T>。也许这在某个时候已经改变了,因为 @Chrᴉz 也指出了这一点,但我无法确认。我添加了一个链接到提交,以便有更多的上下文。 - Jan Aagaard
2个回答

7

区别在于起始线程上下文。

这里是一个简单的示例。

using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        string r;
        OutputThreadInfo("Main");
        r = MyMethod().Result;
        r = Task.Run( () => MyMethod() ).Result;
    }

    public static async Task<string> MyMethod()
    {
        OutputThreadInfo("MyMethod");
        await Task.Delay(50);
        return "finished";
    }

    private static void OutputThreadInfo(string context)
    {
        Console.WriteLine("{0} {1}",context,System.Threading.Thread.CurrentThread.ManagedThreadId);
    }
}

.NET Fiddle

输出结果如下:

Main 32
MyMethod 32
MyMethod 63

第一次调用 MyMethod 将在与 Main 相同的线程上启动,并且如果从具有同步上下文的线程启动,则会阻塞。

第二次调用 MyMethod 将从不同的线程(来自线程池的工作线程)启动,该线程没有同步上下文,因此不会阻塞。

注意,控制台应用程序默认情况下没有同步上下文,但 WinForms、WPF 和 UWP 应用程序都有,并且在异步/等待方面的行为可能会有所不同。


0

Task.ResultTask.Wait会阻塞当前线程,你应该使用await来避免出现问题。(尽管它们只在未完成时才会阻塞)。

第二行代码将创建一个任务,并在线程池中的可用线程上启动其执行,这就是为什么它不会阻塞的原因。
这是因为当使用async-await时,Task构造将生成一个状态机,以跟踪代码块中使用的所有等待,并在所有等待完成后返回结果。请记住,根据您所处的同步上下文,await之后的代码可能会在与任务启动的线程不同的线程上运行。

因此,当我必须同步执行异步方法时,我会使用像这样的一小段代码:

private static readonly TaskFactory _tf = new TaskFactory(
        CancellationToken.None, TaskCreationOptions.None, 
        TaskContinuationOptions.None, TaskScheduler.Default);

public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{      
  return _tf.StartNew<Task<TResult>>((Func<Task<TResult>>) (() =>
  {
    return func();
  })).Unwrap<TResult>().GetAwaiter().GetResult();
}

请记住,如果需要,在RunSync StarNew任务工厂调用中使用相同的CultureInfo,以避免出现此类问题。

我没有使用await的原因是我正在构造函数内调用代码。 - Jan Aagaard
3
如果可能的话,你应该避免这样做。构造函数应该轻量级。如果需要执行大量工作来设置实例(使用任务/异步操作暗示了这一点),则应使用静态的 async Create 方法,并将构造函数设为私有的;如果不可能(因为你使用依赖注入),则应使用异步的 Initialize 方法来完成这项工作。 - ckuri
@ckuri。谢谢。实际上,我在整个代码库中几乎都使用静态Create方法。这很好用,除了测试中我目前使用的Task.Run()语法。可能会迁移到轻量级构造函数和私有初始化方法,再加上一些样板代码,以便类本身在需要时确保初始化。 - Jan Aagaard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接