异步等待如何使用返回值

26

我有一个Windows服务,这个服务是由另一个开发人员继承而来的,运行非常缓慢,并且有许多对eBay API的调用速度很慢。我希望能够加快它的速度,而不需要进行太多的重构。

我刚刚开始尝试使用C#异步/等待来尝试使一些这些缓慢的调用变成异步调用。我想要实现以下内容:

我有一个非常繁忙的方法,会进行许多如下的调用:

getProducts
getCategories
getVehicles
getImages

我的想法是可以将方法简单改为async,并在返回类型中添加Task<T>如下:

public async Task<String> ProcessAdditionalProductDetialsAsync(ItemType oItem)
{
    String additionalProductDetails = string.Empty;

    if (oItem.ItemSpecifics.Count > 0)
    {
        foreach (NameValueListType nvl in oItem.ItemSpecifics)
        {                  
            if (nvl.Value.Count > 0)
            {
                foreach (string s in nvl.Value)
                {
                    additionalProductDetails += "<li><strong>" + nvl.Name + ":</strong>&nbsp;" + s + "</li>";
                }
            }
        }
    }
    return additionalProductDetails;
}

然后使用await调用它们:

Task<String> additionalProductDetials = ebayPartNumbers.ProcessAdditionalProductDetialsAsync(item);
Task<PartNumberCollection> partNumberCollection = ebayPartNumbers.ProcessPartNumbersAsync(item); 


await Task.WhenAll(partNumberCollection, additionalProductDetials);

我怎样才能获取返回类型并使用它们?我已经尝试直接使用partNumberCollection,但只有await属性可用。

1
编译器应该会警告你在ProcessAdditionalProductDetialsAsync中使用了async但没有使用await。这是你的实际代码吗?还是它真的在执行await操作?eBay API在哪里被调用? - Stephen Cleary
1
是的,编译器确实会警告该方法没有等待,但等待在父方法中。 - Alex Stephens
1
你应该注意编译器的警告;如果方法是异步的,那么其中应该有一个 await。如果你想在后台线程上执行代码,请使用 Task.Run;如果你想进行 I/O 操作(例如 eBay API),则应该使用自然异步的方法。 - Stephen Cleary
4个回答

23

使用Task类的Result属性:

await Task.WhenAll(partNumberCollection, additionalProductDetials);

var partNumberCollectionResult = partNumberCollection.Result;
var additionalProductDetialsResult = additionalProductDetials.Result;

5
为避免在“AggregateException”中封装异常,请使用await而不是Result - Stephen Cleary

17
如果通过Task.WhenAll返回的任务已完成,那么您传递给它的所有任务都已完成。这反过来意味着您可以使用每个任务的Result属性,而不必担心被阻塞。
string details = additionalProductDetials.Result;

或者,为了与其他异步代码保持一致性,您可以等待任务完成:

string details = await additionalProductDetials;

再次强调,这个操作保证不会阻塞 - 如果以后出于某种原因你要移除Task.WhenAll(例如你满意使用这些细节来启动另一个任务,在获取零件编号集合之前),你不需要更改代码。


11

您的async方法缺少await操作符,因此会同步运行。如果您正在调用非阻塞API,则可以使用Task.Run()在后台线程上执行CPU绑定工作。

public async Task<String> ProcessAdditionalProductDetialsAsync(ItemType oItem)
{
    return await Task.Run(() =>
    {
        String additionalProductDetails = string.Empty;

        if (oItem.ItemSpecifics.Count > 0)
        {
            foreach (NameValueListType nvl in oItem.ItemSpecifics)
            {
                if (nvl.Value.Count > 0)
                {
                    foreach (string s in nvl.Value)
                    {
                        additionalProductDetails += "<li><strong>" + nvl.Name + ":</strong>&nbsp;" + s + "</li>";
                    }
                }
            }
        }
        return additionalProductDetails;
    });
}

并获得结果

var detail = await ProcessAdditionalProductDetialsAsync(itemType);
var result = ProcessAdditionalProductDetialsAsync(itemType).Result;

调用此方法的父方法标记为async并使用await Task.WhenAll,那么这不会在异步之上运行该方法吗? - Alex Stephens

0

尝试这段代码:

public async Task<String> ProcessAdditionalProductDetialsAsync(ItemType oItem) { 
    String additionalProductDetails = await Task.Run(() => {
       if (oItem.ItemSpecifics.Count > 0) {
          foreach (NameValueListType nvl in oItem.ItemSpecifics) { 
             if (nvl.Value.Count > 0) {
                 string retval = String.Empty;

                 foreach (string s in nvl.Value) {
                     retval += "<li><strong>" 
                       + nvl.Name + ":</strong>&nbsp;" + s + "</li>";
                 }
             }
          }
       }
       return retval;
     }
     return additionalProductDetails;
 }

使用方法:

private async void GetAdditionalProductDetailsAsync(Action<string> callback) {
   string apd = await ProcessAdditionalProductDetialsAsync();
   callback(apd);
}

private void AdditionalProductDetailsRetrieved(string apd) {
    // do anything with apd
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接