我有以下代码,打算异步运行。我的目标是根据需要并行调用GetPictureForEmployeeAsync()
。我想确保CreatePicture上的“ await”不会阻止我这样做。
public Task<Picture[]> GetPictures(IDictionary<string, string> tags)
{
var query = documentRepository.GetRepositoryQuery();
var employees = query.Where(doc => doc.Gender == tags["gender"]);
return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
}
private Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
{
var base64PictureTask = blobRepository.GetBase64PictureAsync(employee.ID.ToString());
var documentTask = documentRepository.GetItemAsync(employee.ID.ToString());
return CreatePicture(tags, base64PictureTask, documentTask);
}
private static async Task<Picture> CreatePicture(IDictionary<string, string> tags, Task<string> base64PictureTask, Task<Employee> documentTask)
{
var document = await documentTask;
return new Picture
{
EmployeeID = document.ID,
Data = await base64PictureTask,
ID = document.ID.ToString(),
Tags = tags,
};
}
如果我正确理解,Task.WhenAll()
不会受到CreatePicture()
中两个等待的任务的影响,因为没有等待GetPictureForEmployeeAsync()
。我对吗?如果没有,我应该如何重组代码以实现我想要的?
答案 0 :(得分:3)
我想确保CreatePicture上的“ await”不会阻止我这样做。
不是。
如果我正确理解,则Task.WhenAll()不受CreatePicture()中两个等待的任务影响,因为未等待GetPictureForEmployeeAsync()。我对吗?
是,不是。 WhenAll
不受CreatePicture
中等待的任务的任何限制,但是与是否等待GetPictureForEmployeeAsync
无关。就行为而言,这两行代码是等效的:
return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
return Task.WhenAll(employees.Select(async employee => await GetPictureForEmployeeAsync(employee, tags)));
我建议您阅读async intro,以更好地了解async
和await
如何处理任务。
此外,由于GetPictures
具有非平凡的逻辑(GetRepositoryQuery
并求值tags["gender"]
),因此我recommend using async
and await
表示GetPictures
,如下:
public async Task<Picture[]> GetPictures(IDictionary<string, string> tags)
{
var query = documentRepository.GetRepositoryQuery();
var employees = query.Where(doc => doc.Gender == tags["gender"]);
var tasks = employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)).ToList();
return await Task.WhenAll(tasks);
}
最后一点,如果您不传递“本来就该等待的任务”,则可以找到代码清理器,而是先await
个并传递它们的结果值:
async Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
{
var base64PictureTask = blobRepository.GetBase64PictureAsync(employee.ID.ToString());
var documentTask = documentRepository.GetItemAsync(employee.ID.ToString());
await Task.WhenAll(base64PictureTask, documentTask);
return CreatePicture(tags, await base64PictureTask, await documentTask);
}
static Picture CreatePicture(IDictionary<string, string> tags, string base64Picture, Employee document)
{
return new Picture
{
EmployeeID = document.ID,
Data = base64Picture,
ID = document.ID.ToString(),
Tags = tags,
};
}
答案 1 :(得分:1)
调用异步方法时要记住的一点是,一旦在该方法中到达await
语句,控件立即返回到调用该方法的代码。异步方法-无论await语句恰好位于方法中的任何位置。使用“普通”方法,控制不会返回到调用该方法的代码,直到到达该方法的末尾。
因此,您可以执行以下操作:
private async Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
{
// As soon as we get here, control immediately goes back to the GetPictures
// method -- no need to store the task in a variable and await it within
// CreatePicture as you were doing
var picture = await blobRepository.GetBase64PictureAsync(employee.ID.ToString());
var document = await documentRepository.GetItemAsync(employee.ID.ToString());
return CreatePicture(tags, picture, document);
}
由于GetPictureForEmployeeAsync的第一行代码有一个await
,因此控件将立即回到该行...
return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
...只要被调用。这将影响所有员工项目的并行处理(嗯,分配给您的应用程序的线程数将受到限制)。
另一个建议是,如果此应用程序正在访问数据库或Web服务以获取图片或文档,则此代码可能会导致可用连接用尽的问题。如果是这种情况,请考虑使用System.Threading.Tasks.Parallel
并设置最大并行度,或使用SemaphoreSlim
控制同时使用的连接数。