以下代码编译并运行良好。但是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());
}
}
答案 0 :(得分: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
对象由编译器生成的代码构成,因此您不必这样做。