1

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

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

4 回答 4

2

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

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

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

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

确切地说,类型 T 的结果将在未来如何产生是特定任务的实现细节;[...]

解决方案的语言部分是新的 await 关键字。常规方法调用意味着“记住你在做什么,运行这个方法直到它完全完成,然后从你离开的地方继续,现在知道方法的结果。” 相比之下,await 表达式的意思是“评估此表达式以获得一个对象,该对象表示将来会产生结果的工作。将当前方法的其余部分注册为与该任务的继续相关的回调。一旦生成了任务并注册了回调,立即将控制权返回给我的调用者。”</p>

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

[...] Visual Basic 和 C# [...] 让您表达不连续的顺序代码。[...] 当 Visual Basic 或 C# 编译器获取异步方法时,它会在编译期间对其进行相当多的破坏:底层运行时不直接支持该方法的不连续性,并且必须由编译器模拟。因此,您不必将方法分解成小块,编译器会为您完成。[...]

编译器将您的异步方法转换为状态机。状态机跟踪您在执行中的位置以及您的本地状态。[...]

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

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

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

于 2013-04-08T11:50:52.227 回答
0

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

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方法体中,因为这段代码可以在调用该方法的线程上调用。Task 具有等价物,可以替换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来查看生成的代码。

另外我真的推荐阅读这篇文章异步编程的最佳实践

于 2013-04-08T08:24:19.063 回答
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;
}

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

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

于 2013-04-08T19:06:59.210 回答
0

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

异步方法具有三种可能的返回类型:Task<TResult>, 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;
}
于 2013-04-08T08:21:07.633 回答