正确的同步并行任务的方法

时间:2015-06-30 21:23:46

标签: c# .net async-await task-parallel-library

目前我们的代码工作正常:

Result result1 = null;
Result result2 = null;

var task1 = Task.Factory.StartNew(()=>
{
   var records = DB.Read("..");
   //Do A lot
   result1 = Process(records);  
}); 

var task2 = Task.Factory.StartNew(()=>
{
   var records = DB.Read(".....");
   //Do A lot
   result2 = Process(records);  
});

Task.WaitAll(task1, task2);

var result = Combine(result1, result2);

现在我们想使用数据库函数的异步副本,我们正在使用这个新模式:

Result result1 = null;
Result result2 = null;

var task1 = await Task.Factory.StartNew( async ()=>
{
   var records = await DB.ReadAsync("..");
   //Do A lot
   result1 = Process(records);  
}); 

var task2 = await Task.Factory.StartNew(async ()=>
{
   var records = await DB.ReadAsync(".....");
   //Do A lot
   result2 = Process(records);  
});

Task.WaitAll(task1, task2);

var result = Combine(result1, result2);

在我们切换到异步后,我们开始观察异常行为。所以我想知道这是否是正确的并行化异步调用模式?

3 个答案:

答案 0 :(得分:2)

Task.Factory.StartNew是一个异步前API。您应该使用设计为async-await的Task.Run

var task1 = await Task.Run( async ()=>
{
   var records = await DB.ReadAsync("..");
   //Do A lot
   result1 = Process(records);  
});

问题是异步lambda会返回Task,因此Task.Factory.StartNew会返回Task<Task>(外部因为Task.Factory.StartNew返回Task而内部task1一个是async lambda的结果。)

这意味着当你等待task2Task.Unwrap时,你并没有真正等待整个操作,只是它的同步部分。

您可以在返回的Task<Task>上使用Task<Task> task1 = await Task.Factory.StartNew(async ()=> { var records = await DB.ReadAsync(".."); //Do A lot result1 = Process(records); }); Task actualTask1 = task1.Unwrap(); await actualTask1; 来解决此问题:

Task.Run

但是Task.Run会隐含地为你做这件事。

作为旁注,您应该意识到您不需要Task.When同时执行这些操作。您可以通过调用这些方法并等待结果async Task MainAsync() { var task1 = FooAsync(); var task2 = BarAsync(); await Task.WhenAll(task1, task2); var result = Combine(task1.Result, task2.Result); } async Task<Result> FooAsync() { var records = await DB.ReadAsync(".."); //Do A lot return Process(records); } async Task<Result> BarAsync() { var records = await DB.ReadAsync("....."); //Do A lot return Process(records); }

来实现
Task.Run

如果您需要将这些方法的同步部分(第一个await之前的部分)卸载到ThreadPool,则只需要static async Task RunAsync() { string _user; string _password; string _authorizationType; string _contentType; string _CredentialsToBase64; string _url = "https://datafeed-api.dynatrace.com"; _user = "MYUSERNAME"; _password = "MYPASSWORD"; _authorizationType = "basic"; _contentType = "application/json"; _CredentialsToBase64 = System.Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(_user + ":" + _password)); using (var client = new HttpClient()) { client.BaseAddress = new Uri(_url); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(_contentType)); client.DefaultRequestHeaders.Add("Authorization", _authorizationType + " " + _CredentialsToBase64); using (HttpResponseMessage httpResponse = await client.GetAsync("publicapi/rest/v1.0/login?user=MYUSERNAME&password=MYPASSWORD HTTP/1.1")) { if (httpResponse.IsSuccessStatusCode) { Console.WriteLine("Success"); } else { Console.WriteLine(string.Format("Service request failed ({0})", httpResponse.StatusCode)); } } }

答案 1 :(得分:0)

Task.Factory.StartNew启动执行另一个独立执行单元的新任务。所以最简单的处理方式可能如下:

var task1 = Task.Factory.StartNew(()=> //NO AWAIT
{
   var records = DB.Read("....."); //NO ASYNC
   //Do A lot
   result1 = Process(records);  
});

... another task definition

Task.WaitAll(task1, task2);

在一个任务中读取并处理顺序,因为您具有数据依赖性。

答案 2 :(得分:0)

使用.WaitAll不是异步编程,因为你实际上在等待时阻止当前线程。你也不要打电话。解包这就是为什么你只等待创建异步lambda,而不是等待异步lambda本身。

Task.Run可以为你解包async lambda。但是,这是一种更简单,更清洁的方式。

var task1 = DB.ReadAsync("..").ContinueWith(task => {
   //Do A lot
   return Process(task.Result);  
}, TaskScheduler.Default);

var task2 = DB.ReadAsync("..").ContinueWith(task => {
   //Do A lot
   return Process(task.Result);  
}, TaskScheduler.Default);

var result = Combine(await task1, await task2);

通过这种方式,您可以准确地获得结果。所以你根本不需要额外的任务和变量。

请注意,ContinueWith是一个棘手的函数,它适用于TaskScheduler.Current,如果它不为null,否则它适用于TaskScheduler.Default,它是线程池调度程序。因此,在调用此函数时始终显式指定调度程序更安全。

同样对于claryfing我没有包含错误检查,因为实际上DB.ReadAsync可以通过错误完成。但这很容易,你可以自己处理。