目前我们的代码工作正常:
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);
在我们切换到异步后,我们开始观察异常行为。所以我想知道这是否是正确的并行化异步调用模式?
答案 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的结果。)
这意味着当你等待task2
和Task.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可以通过错误完成。但这很容易,你可以自己处理。