Task对象的return语句在哪里?

时间:2013-04-08 07:28:28

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

以下代码编译并运行良好。但是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());
}
}

4 个答案:

答案 0 :(得分:2)

虽然你的问题陈述了显而易见的 - 代码编译 - 而其他答案试图通过示例解释,我认为答案最好在以下两篇文章中描述:

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

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

         

    CLR 4版本定义了类型Task [...]来表示   “将会产生T型结果的一些工作”的概念   未来。“工作的概念将在未来完成但是   返回没有结果“由非通用任务类型表示。

         

    确切地说,如何生成类型T的结果   future是特定任务的实现细节; [...]

         

    解决方案的语言一半是新的await关键字。经常   方法调用意味着“记住你在做什么,运行这个方法直到   它完全结束了,然后在你离开的地方接着,现在   知道方法的结果。“等待表达,相反,   表示“评估此表达式以获取表示工作的对象   这将在未来产生一个结果。注册其余部分   当前方法作为与继续关联的回调   那个任务。生成任务并注册回调后,   立即将控制权交还给我的来电者。“

    2.引擎盖下的解释见:http://msdn.microsoft.com/en-us/magazine/hh456403.aspx

      

    [...] Visual Basic和C#[...]让你表达不连续   顺序代码。 [...]当Visual Basic或C#编译器被保持时   在一个异步方法中,它会在很长一段时间内破坏它   编译:不直接支持该方法的不连续性   由底层运行时,必须由编译器模拟。所以   而不是你必须把方法分成比特,编译器   为你做到了。 [...]

         

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

         

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

         

    从编译器的角度来看,生成任务很容易。   它依赖于框架提供的任务构建器的概念,可在以下位置找到   System.Runtime.CompilerServices [...]构建器允许编译器   获取一个任务,然后让它用结果或一个完成任务   例外。 [...]任务构建器是仅适用于的特殊帮助程序类型   编译器消耗。 [...]

         

    [...]围绕生产和消费建立一个状态机   任务。基本上,来自原始方法的所有用户逻辑   被收入代表,但当地人的声明   被解除,所以他们可以在多次调用中存活下来。此外,   引入一个状态变量来跟踪事情的进展,   并且恢复委托中的用户逻辑包含在一个大的   查看状态的开关并跳转到相应的标签。所以   每当调用恢复时,它将立即跳回到它的位置   离开了最后一次。

答案 1 :(得分:0)

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

  

异步方法有三种可能的返回类型:Task<TResult>, Task, void

如果您需要任务,则必须指定return语句。摘自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;
}

答案 2 :(得分:0)

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

答案 3 :(得分:0)

这正是async关键字的含义。它需要一种方法并且(通常)将其转换为Task返回方法。如果普通方法的返回类型为void,则async方法将具有Task。如果返回类型为其他T,则新的返回类型将为Task<T>

例如,要同步下载某个数据进程,您可以编写如下内容:

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<Foo>,但您仍然只返回Foo。与void类似 - 返回方法:因为它们不必包含return语句,async Task(没有任何<T>)方法也不会。

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