[英]understanding Async and Await in C#
我正在尝试使用C#学习Async和Await。 我有三个方法,当我尝试调用所有这三个方法时,这使我想知道异步调用实际上如何在C#中工作。
class Content {
public async Task<string> Delay1() {
await Task.Delay(5000);
return "hello";
}
public async Task<string> Delay2() {
await Task.Delay(5000);
return "hello";
}
public async Task<string> Delay3() {
await Task.Delay(5000);
return "hello";
}
public async Task Print() {
Console.WriteLine(await Delay1());
Console.WriteLine(await Delay2());
Console.WriteLine(await Delay3());
}
}
因此,当我调用此方法时:
static void Main() {
new Content().Print();
Console.Read();
}
我应该在延迟5秒后同时获取所有这三种方法,但是我的Delay2()
方法在Delay1()
方法的5秒后被调用,而Delay3()
方法在Delay2()
5秒后被调用Delay2()
方法。
有人可以帮助我了解C#中Async方法的工作吗?
UPDATE 1 Async和Await假设是异步的,那么为什么我的常规语句被停止执行
public async Task Print()
{
Console.WriteLine(await Delay1());
Console.WriteLine("this is regular flow");
}
异步不是并发的。 每次等待调用都将阻止执行以下代码,直到任务完成。 在这种情况下,您可以通过将示例更改为如下形式来创建并发
class Program
{
static void Main()
{
new Content().Print().Wait();
Console.Read();
}
}
class Content
{
public async Task<string> Delay1()
{
await Task.Delay(5000);
return "hello";
}
public async Task<string> Delay2()
{
await Task.Delay(5000);
return "hello";
}
public async Task<string> Delay3()
{
await Task.Delay(5000);
return "hello";
}
public async Task Print()
{
var tasks = new[] {Delay1(), Delay2(), Delay3()};
await Task.WhenAll(tasks);
foreach(var result in tasks.Select(x => x.Result))
{
Console.WriteLine(result);
}
}
}
您可以彼此独立地启动三个任务,并将它们存储在一个集合中。 然后,您可以调用await Task.WhenAll
来阻止执行,直到所有这些任务都完成为止。 之后,您可以遍历结果并根据需要使用它们。
async / await语法允许编写一个比不可读的模式更具可读性的模式。 为了使您理解其真正含义,请考虑将您的代码段重写如下:
class Content
{
public void Delay1(Action<string> callback)
{
//something which takes 5000ms
callback("hello");
}
public void Delay2(Action<string> callback)
{
//something which takes 5000ms
callback("hello");
}
public void Delay3(Action<string> callback)
{
//something which takes 5000ms
callback("hello");
}
public void Print(Action callback)
{
this.Delay1(x=> {
Console.WriteLine(x);
this.Delay2(y => {
Console.WriteLine(y);
this.Delay3(z=> {
Console.WriteLine(z);
callback();
});
});
});
}
}
您不应中继系统实际执行的操作(例如,对于Task.Delay)。 它可以将“延迟”中继到其他线程(用于ThreadPool的队列),但是它可以与调用方相同的方式执行作业。
正如@Rob正确指出的那样,异步不是并发的:代码按顺序执行。
只是让您了解而已:仅此而已。
当您调用await时,您正在要求curret方法停止执行,直到“ awaited”调用结束; 所以,当你说:
Console.WriteLine(await Delay1());
您正在要求当前方法(打印)停止,直到Delay1()完成。
为了获得您期望的结果,您必须在不使用await指令的情况下调用它们(使用await调用异步方法不是强制性的)。
这是一个例子:
public class Content
{
public async void Delay1()
{
await Task.Delay(5000);
Console.WriteLine("hello");
}
public async void Delay2()
{
await Task.Delay(5000);
Console.WriteLine("hello");
}
public async void Delay3()
{
await Task.Delay(5000);
Console.WriteLine("hello");
}
public void Print()
{
Delay1();
Delay2();
Delay3();
}
}
编辑:正如@Adrian指出的,Print方法可以比三个Delay方法调用更快地完成。 由于Console.ReadLine();我在测试中没有注意到它;
这是供您查看的摘录。 将其放入控制台应用程序进行测试。
此样本中有两个工作流程,第一个工作流程模仿您的模式(使用助手)。 另一个显示了对whenAll任务使用await以及在初始化类型(在本例中为动态)的分配中使用结果。
使用await
的强调概念只是为了确保在继续执行该功能之前,已完成任务。 'Async'关键字确保函数的作用域将在任务中(因此返回任务),并且将有任务执行该作用域(因此需要await
)。
如果您不打算在继续之前完成任务,请不要使用async/await
namespace TaskAsyncTests
{
using System.Threading.Tasks;
class Program
{
static async Task<KeyValuePair<string, long>> TaskThis(Func<string> fn)
{
var watch = new System.Diagnostics.Stopwatch();
watch.Start();
var task = Task.Run(fn); //fn will be 1sec
await Task.Delay(1000); //also being delayed 1sec here
var result = await task;
watch.Stop();
return new KeyValuePair<string, long>(result, watch.ElapsedMilliseconds); //result should only be approx. 1 sec though
}
static void Main(string[] args)
{
var watch = new System.Diagnostics.Stopwatch();
watch.Start();
var results = Run(Task.WhenAll(new[]
{
TaskThis(LongProcessingFunction),
TaskThis(LongProcessingFunction),
TaskThis(LongProcessingFunction),
}));
watch.Stop();
foreach (KeyValuePair<string, long> item in results)
{
Console.WriteLine(@"result:= '{0}' ElapsedMilliseconds := {1}", item.Key, item.Value.ToString());
}
Console.WriteLine("total ElapsedMilliseconds := {0}", watch.ElapsedMilliseconds);
watch.Reset();
watch.Start();
var result = Run(GetSomethingAsync());
watch.Stop();
Console.WriteLine(@"result->PropertyOne := '{0}' ElapsedMilliseconds := {1}", result.PropertyOne.Key, result.PropertyOne.Value.ToString());
Console.WriteLine(@"result->PropertyTwo := '{0}' ElapsedMilliseconds := {1}", result.PropertyTwo.Key, result.PropertyTwo.Value.ToString());
Console.WriteLine(@"result->PropertyThree := '{0}' ElapsedMilliseconds := {1}", result.PropertyThree.Key, result.PropertyThree.Value.ToString());
Console.WriteLine("total ElapsedMilliseconds := {0}", watch.ElapsedMilliseconds);
Console.ReadLine();
}
static string LongProcessingFunction()
{
Task.Delay(1000).Wait();
return "Hello World";
}
static T Run<T>(Task<T> taskRunner)
{
return taskRunner.Result;
}
static T[] Run<T>(Task<T[]> taskRunner)
{
return taskRunner.Result;
}
static async Task<dynamic> GetSomethingAsync()
{
var resultsTask = Task.WhenAll(new[]
{
TaskThis(LongProcessingFunction),
TaskThis(LongProcessingFunction),
TaskThis(LongProcessingFunction)
}).ConfigureAwait(false);
// do other stuff here
Task.Delay(2000).Wait();
var results = await resultsTask;
return new
{
PropertyOne = results[0],
PropertyTwo = results[1],
PropertyThree = results[2]
};
}
}
}
我的结果是:
result:= 'Hello World' ElapsedMilliseconds := 1025
result:= 'Hello World' ElapsedMilliseconds := 1014
result:= 'Hello World' ElapsedMilliseconds := 1014
total ElapsedMilliseconds := 1028
result->PropertyOne := 'Hello World' ElapsedMilliseconds := 1001
result->PropertyTwo := 'Hello World' ElapsedMilliseconds := 1001
result->PropertyThree := 'Hello World' ElapsedMilliseconds := 1000
total ElapsedMilliseconds := 2001
用引文更新
由于我的答案因我不知道的原因而被否决,因为除罗伯之外,拒绝投票的人没有给出任何理由要求除罗斯外,我在这里为我的答案辩护。
这里的目的不是要证明任何人是错误的,而是要确保OP接收到正确的信息和事实。
那么,OP的问题是什么? 我认为有两个问题:
发布了许多答案来修复代码。 纳尔逊的答案也具有正确的代码,因此我从未投票过。 我的问题不是这里提供的解决方案,而是“异步不是并发”的说法。 我还想用简单的英文解释异步等待的工作原理,因为OP明确要求这样做。 所以这就是为什么我仍然坚持自己的立场的原因:
一位受人尊敬的作者,SO成员Stephen Cleary在其关于并发的书中指出了这一点:
异步编程
一种使用期货或回调来避免不必要的线程的并发形式。
未来(或承诺)是表示将来将要完成的某些操作的类型。 .NET中现代的未来类型是“任务”和“任务”。 较早的异步API使用回调或事件而不是期货。 异步编程以异步操作的思想为中心:启动的某些操作将在一段时间后完成。 在进行操作时,它不会阻塞原始线程; 启动操作的线程可以自由执行其他工作。 操作完成后,它会通知其将来或调用其完成回调事件,以使应用程序知道操作已完成。
他进一步说:
异步编程是一种强大的并发形式,但是直到最近,它仍需要极其复杂的代码。 VS2012中的async和await支持使异步编程几乎与同步(非并发)编程一样容易。
这是一个stackoverflow答案,其中同一作者回答了另一个类似的问题。
这是本书的链接,而我引用的部分来自1.1。 并发简介。
现在应该阐明并支持我关于异步并发的声明。
我不确定我的“示例与并发无关”,但Rob可以对此进行解释。
我不确定答案中还有哪些其他误导性内容 ,所以我无法为自己辩护。 我不认为我有任何误导性陈述,但如果有的话,我,OP和其他成员都希望了解他们,以便它可以使所有人受益。
引用结束更新
。
这是不正确的,因为其他一些回答表明异步不是并发形式 。 异步编程IS(是的)是一种并发形式。 但是,话虽如此,这并不意味着这项工作将需要更少的时间。 这项工作仍将花费相同的时间(由于上下文切换,可能会更长)。 但是诀窍在于,将在不等待先前工作完成的情况下同时完成工作(先延迟1然后再延迟2然后再延迟3)。 让我用您的代码解释所有这些。
这是您在Print方法中的代码中所做的事情:让我们调用执行Print P1的线程
因此,您的打印方法将调用Delay1并等待其返回。 然后,它调用Delay2并等待它返回。 然后3并等待它返回。 所有这些都将在睡眠5秒后返回(每个)。
因此,您可能会问:“那么这一切的意义何在?”?
关键是在您的打印方法中,您可以同时调用所有它们:执行Delay1,Delay2和Delay3,当它们全部完成后,转到下一行。
那么,那能给您带来什么呢?
好吧,如果您在Delay方法中调用某些Web服务,并且每个Web服务都需要10秒才能返回,那么您将在几乎同一时间全部调用它们,因此这三个都将开始工作。 他们可能会在10秒多一点后全部返回。 因此,您基本上将它们全部同时发送给他们,因此“ 并发 ”。 如果不异步调用它们,则Delay1需要完成(10秒),然后完成Delay2(10秒),然后完成Delay3(10秒),因此总共需要30秒。
像这样思考:
您是麦当劳的经理。 您有很多员工(线程池),需要他们来工作(Delay1,Delay2和Delay3)。 最好是先让一名员工先执行Delay1,然后再执行Delay2,然后再执行Delay3,或者分配3个不同的员工(线程池中的线程),然后让1名员工执行Delay1,然后告诉第二名员工执行Delay2,而不要等待Delay1完成并询问第三名员工要做Delay3。 显然,您会选择3名员工。
现在在线程池的世界中有所不同。 可能为2个任务选择了相同的线程,因为一个线程完成了该工作,然后回到池中,然后又被赋予了另一个任务。 在您的示例中,这种情况极不可能发生,因为他们需要睡眠5秒钟。 只有一种方法可以做到这一点。
请注意,在某些情况下,一项任务取决于另一项任务,您别无选择,只能等待。 例如,如果Delay1正在从远程计算机上获取文件,而Delay2正在获取另一个文件,并且Delay3将同时处理这两个文件,则Delay1和Delay2可以“同时执行”,但是Delay3需要等待它们返回才能开始。
为避免混淆,请注意,在您的情况下,T1,T2和T3仅在睡眠。 您好的返回将由P1线程完成。 有一些方法可以使P1不必这样做,但是在您的情况下,一旦完成睡眠,控制权将交给P1。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.