其中是Task对象的返回状态

本文关键字:返回 状态 对象 Task | 更新日期: 2023-09-27 18:08:12

以下代码编译并运行良好。但是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对象的返回状态

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

  1. "高于表面"的答案-全文如下:http://msdn.microsoft.com/en-us/magazine/hh456401.aspx

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

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

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

解决方案的语言部分是新的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[…]生成器允许编译器获取一个Task,然后让它完成该Task并返回一个结果或例外[…]任务生成器是专门用于编译器消耗。[…]

[…]围绕生产和消费建立一个状态机的任务。从本质上讲,来自原始方法的所有用户逻辑被放入恢复委托中,但本地人的声明被取出,这样它们就可以在多次调用中幸存下来。此外引入了一个状态变量来跟踪事情发展到什么程度,并且恢复委托中的用户逻辑被包裹在一个大开关,该开关查看状态并跳到相应的标签。所以每当调用恢复时,它都会跳回到它所在的位置上次中断。

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

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

如果需要Task,则必须指定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;
}

async关键字种类告诉编译器应该将方法主体用作Task主体。简单地说,我们可以说这些都相当于您的例子:

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);
                }
           });
}

当然,你的方法的编译结果将与我的例子完全不同,因为编译器足够聪明,它可以用这种方式生成代码,所以你的方法中的一些行将在上下文(线程(上调用,上下文调用该方法,其中一些行在后台上下文上调用。这就是为什么不建议在async方法的主体中使用Thread.Sleep(1000);的原因,因为可以在调用此方法的线程上调用此代码。任务具有等价的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来看看生成的代码。

此外,我真的建议阅读这篇文章异步编程中的最佳实践

这正是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对象是由编译器生成的代码构建的,所以您不必这样做。