转换使用Tasks的便捷方法

时间:2015-01-30 20:31:41

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

我经常编写具有便捷方法的代码,基本上包装其他方法。这是一个简单的例子:

public class WithoutAsync
{
    public static ReadOnlyCollection<Response> GetResponses(IEnumerable<Request> fromRequests)
    {
        var ret = new List<Response>();

        foreach (Request r in fromRequests)
        {
            ret.Add(new Response());
        }

        return ret.AsReadOnly();
    }

    //convenience method
    public static Response GetResponse(Request fromRequest)
    {
        return GetResponses(new Request[] {fromRequest})[0];
    }
}

现在我想await长时间运行但我无法弄清楚如何改进此方法以用于TPL:

public class WithAsync
{
    public static async Task<ReadOnlyCollection<Response>> GetResponses(IEnumerable<Request> fromRequests)
    {
        var awaitableResponses = new List<Task<Response>>();

        foreach (Request r in fromRequests)
        {
            awaitableResponses.Add(Task.Run<Response>(async () =>
                {
                    await Task.Delay(10000); //simulate some long running async op.
                    return new Response();
                }));
        }

        return new List<Response>(await Task.WhenAll(awaitableResponses)).AsReadOnly();
    }

    //convenience method
    public static Task<Response> GetResponse(Request fromRequest)
    {
        return GetResponse(new Request[] { fromRequest });
    }
}

上面的便捷方法显然不会起作用,因为当它确实需要返回Task<ReadOnlyCollection<Response>>时,它会尝试返回Task<Response>

这有效:

//convenience method
public static Task<Response> GetResponse(Request fromRequest)
{
    return new Task<Response>(new Func<Response>(() => GetResponse(new Request[] { fromRequest }).Result[0]));
}

但它看起来真的很尴尬,更重要的是,它会在.Result[0]上阻塞,这可能是在UI线程上。

有没有什么好办法可以完成我想要做的事情?

2 个答案:

答案 0 :(得分:5)

你试图避免使用“方便方法”async,但没有理由这样做。

你想要的是调用另一种方法,等到有响应,然后得到第一个也是唯一一个。您可以通过async并使用await

来实现这一目标
async Task<Response> GetResponseAsync(Request fromRequest)
{
    var responses = await GetResponsesAsync(new[] { fromRequest });
    return responses.Single();
}

虽然在这种特定情况下更好的解决方案是切换周围的事情并让单个GetResponse实际执行单个请求的工作,并让多个GetRsponses调用它:

async Task<ReadOnlyCollection<Response>> GetResponsesAsync(IEnumerable<Request> fromRequests)
{
    return (await Task.WhenAll(fromRequests.Select(GetResponse))).ToList().AsReadOnly();
}

async Task<Response> GetResponseAsync(Request fromRequest)
{
    await Task.Delay(10000); //simulate some long running async op.
    return new Response();
}

注意:

  • 我知道这是一个例子,但可能没有理由使用Task.Run而不是简单的async来电。
  • 惯例是将async方法命名为“Async”后缀(即GetResponseAsync)。
  • 我还复制了返回集合的方法的名称。

答案 1 :(得分:0)

我仍然坚持I3arnon的答案,因为它写得很好,信息量很大,但我想提交自己的答案,因为我意识到我差不多那里。这是我努力寻找的async便利方法:

//convenience method
public static async Task<Response> GetResponse(Request fromRequest)
{
    return (await GetResponses(new Request[] { fromRequest }))[0];
}