【问题标题】:Where is the return statment for the Task object?Task 对象的返回语句在哪里?
【发布时间】:2013-04-09 03:06:59
【问题描述】:

以下代码编译并运行良好。但是 Consumer() 和 Producer() 方法的 return 语句在哪里?

class Program
{
    static BufferBlock<Int32> m_buffer = new BufferBlock<int>(
        new DataflowBlockOptions { BoundedCapacity = 10 });

public static async Task Producer()   <----- How is a Task object returned?
{
    while (true)
    {
        await m_buffer.SendAsync<Int32>(DateTime.Now.Second);
        Thread.Sleep(1000);
    }
}


public static async Task Consumer() <----- How is a Task object returned?
{
    while (true)
    {
        Int32 n = await m_buffer.ReceiveAsync<Int32>();
        Console.WriteLine(n);
    }
}


static void Main(string[] args)
{
    Task.WaitAll(Consumer(), Producer());
}
}

【问题讨论】:

  • 但是 Task.WaitAll() 只接受 Tasks 作为其参数。但是编译器似乎完全可以接受。
  • 我认为 While(true) 无关紧要。因为我删除了 while(true) 循环,所以它仍然有效。

标签: c# task-parallel-library c#-5.0


【解决方案1】:

虽然您的问题很明显 - 代码可以编译 - 并且其他答案试图通过示例进行解释,但我认为以下两篇文章最好地描述了答案:

  1. “表面上”的答案 - 全文在这里:http://msdn.microsoft.com/en-us/magazine/hh456401.aspx

[...] C# 和 Visual Basic [...] 为编译器提供了足够的提示 在幕后为您建立必要的机制。这 解决方案有两个部分:一个在类型系统中,一个在 语言。

CLR 4 版本定义了类型 Task [...] 来表示 “一些工作将产生类型 T 的结果 未来。” “将在未来完成的工作,但 不返回结果”由非泛型 Task 类型表示。

类型 T 的结果将如何在 未来是特定任务的实现细节; [...]

解决方案的语言部分是新的 await 关键字。一个普通的 方法调用的意思是“记住你在做什么,运行这个方法直到 它已经完全完成了,然后从你离开的地方继续,现在 知道方法的结果。”相比之下,await 表达式, 意思是“评估这个表达式以获得一个代表作品的对象 这将在未来产生结果。注册剩余的 当前方法作为回调关联的延续 那个任务。生成任务并注册回调后, 立即将控制权交还给我的调用者。”

2.引擎盖下的解释在这里找到:http://msdn.microsoft.com/en-us/magazine/hh456403.aspx

[...] Visual Basic 和 C# [...] 让你表达不连续 顺序代码。 [...] 当 Visual Basic 或 C# 编译器被占用时 一个异步方法,它在 编译:不直接支持方法的不连续性 由底层运行时,并且必须由编译器模拟。所以 编译器不必将方法分成几部分,而是 为你做。 [...]

编译器将您的异步方法转换为状态机。这 状态机跟踪您在执行中的位置以及执行的内容 你当地的州是。 [...]

异步方法产生任务。更具体地说,异步 方法返回 Task 或 Task 类型之一的实例 System.Threading.Tasks,并且该实例是自动生成的。 它不必(也不能)由用户代码提供。 [...]

从编译器的角度来看,生成任务是很容易的部分。 它依赖于框架提供的任务构建器概念,可在 System.Runtime.CompilerServices [...] 构建器让编译器 获得一个任务,然后让它用一个结果或一个 例外。 [...] 任务构建器是特殊的辅助类型,仅用于 编译器消耗。 [...]

[...] 围绕生产和消费建立状态机 的任务。本质上,原始方法中的所有用户逻辑 被放入resumption delegate,但是locals的声明 被提升,因此它们可以在多次调用中存活。此外, 引入了一个状态变量来跟踪事情的进展情况, 并且恢复委托中的用户逻辑被包裹在一个大的 查看状态并跳转到相应标签的开关。所以 每当调用恢复时,它都会立即跳回到它所在的位置 上次离开了。

【讨论】:

    【解决方案2】:

    当方法没有返回语句时,它的返回类型是Task。看看 Async Await 的MSDN page

    异步方法有三种可能的返回类型:Task&lt;TResult&gt;, Task, void

    如果你需要一个任务,你必须指定返回语句。取自 MSDN 页面:

    // TASK<T> EXAMPLE
    async Task<int> TaskOfT_MethodAsync()
    {
       // The body of the method is expected to contain an awaited asynchronous 
       // call. 
       // Task.FromResult is a placeholder for actual work that returns a string. 
       var today = await Task.FromResult<string>(DateTime.Now.DayOfWeek.ToString());
    
       // The method then can process the result in some way. 
       int leisureHours;
       if (today.First() == 'S')
          leisureHours = 16;
       else
         leisureHours = 5;
    
        // Because the return statement specifies an operand of type int, the 
        // method must have a return type of Task<int>. 
        return leisureHours;
    }
    

    【讨论】:

    • 抱歉,我没有在那个 MSDN 页面中看到提到了隐式任务返回值。虽然在 IL 代码中,它确实显式地返回了一个任务。
    【解决方案3】:

    async 关键字告诉编译器方法体应该用作任务体。简单来说,我们可以说这些等同于您的示例:

    public static Task Producer()   <----- How is a Task object returned?
    {
        return Task.Run(() => 
               {
                   while (true)
                   {
                        m_buffer.SendAsync<Int32>(DateTime.Now.Second).Wait();
                        Thread.Sleep(1000);
                   } 
               });
    }
    
    
    public static Task Consumer() <----- How is a Task object returned?
    {
        return Task.Run(() => 
               {
                    while (true)
                    {
                         Int32 n = m_buffer.ReceiveAsync<Int32>().Wait();
                         Console.WriteLine(n);
                    }
               });
    }
    

    当然你方法的编译结果会和我的例子完全不同,因为编译器足够聪明,它可以以这种方式生成代码,所以你方法中的一些行将在上下文(线程)上调用,它在后台上下文中调用方法和其中一些方法。这实际上就是为什么Thread.Sleep(1000);不推荐在async方法体中的原因,因为这段代码可以在调用这个方法的线程上调用。任务具有等效项,可以替换Thread.Sleep(1000); 等效项await Task.Delay(1000),将在后台线程上调用。如您所见,await 关键字保证您将在后台上下文中调用此调用,并且不会阻塞调用者上下文。

    让我们再看一个例子:

    async Task Test1()
    {
        int a = 0; // Will be called on the called thread.
        int b = await this.GetValueAsync(); // Will be called on background thread 
        int c = GetC(); // Method execution will come back to the called thread again on this line.
        int d = await this.GetValueAsync(); // Going again to background thread
    }
    

    所以我们可以说这会生成代码:

    Task Test1()
    {
        int a = 0; // Will be called on the called thread.
        vat syncContext = Task.GetCurrentSynchronizationContext(); // This will help us go back to called thread
    
        return Task.Run(() => 
          {
               // We already on background task, so we safe here to wait tasks
               var bTask = this.GetValueAsync();
               bTask.Wait();
               int b = bTask.Result;
    
               // syncContext helps us to invoke something on main thread
               // because 'int c = 1;' probably was expected to be called on 
               // the caller thread
               var cTask = Task.Run(() => return GetC(), syncContext); 
               cTask.Wait();
               int c = cTask.Result;
    
               // This one was with 'await' - calling without syncContext, 
               // not on the thread, which calls our method.
               var dTask = this.GetValueAsync();
               dTask.Wait();
               int d = dTask.Result;
          });
    }
    

    同样,这与您从编译器获得的代码不同,但它应该只是让您了解它是如何工作的。如果您真的想查看生成的库中的内容,例如使用IlSpy 查看生成的代码。

    另外我真的推荐阅读这篇文章Best Practices in Asynchronous Programming

    【讨论】:

    • 我认为说async 方法类似于使用Task.Run() 是不对的,因为它给出了错误的想法。尤其是在 GUI 应用程序的情况下,行为是非常不同的。
    • 这就是为什么我有最新的例子来展示这在 GUI 应用程序的情况下是如何工作的。显示 AsyncTaskMethodBuilder 不认为它会解释太多。
    【解决方案4】:

    这正是async 关键字的含义。它接受一个方法并(通常)将其转换为 Task 返回方法。如果普通方法的返回类型为void,则async 方法的返回类型为Task。如果返回类型是其他T,则新的返回类型将是Task&lt;T&gt;

    例如,要同步下载某个进程的一些数据,你可以这样写:

    Foo GetFoo()
    {
        string s = DownloadFoo();
        Foo foo = ParseFoo(s);
        return foo;
    }
    

    异步版本将如下所示:

    Task<Foo> GetFoo()
    {
        string s = await DownloadFoo();
        Foo foo = ParseFoo(s);
        return foo;
    }
    

    请注意,返回类型从 Foo 更改为 Task&lt;Foo&gt;,但您仍然只返回 Foo。与void-returning 方法类似:因为它们不必包含return 语句,async Task(没有任何&lt;T&gt;)方法也不需要。

    Task 对象由编译器生成的代码构造,因此您不必这样做。

    【讨论】:

    • 隐式任务返回真的让我很困扰。
    • 为什么? async 的全部意义在于更容易编写异步代码。为此,您需要某种“异步值”,这正是Task 的含义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-30
    • 1970-01-01
    • 2017-03-26
    • 1970-01-01
    相关资源
    最近更新 更多