如何在没有C#的异步的循环中实现这种异步调用模式?

时间:2012-11-19 20:41:57

标签: c# c#-4.0 async-await

我正在尝试实现这个简单的任务,即在C#4中以异步方式列出AmazonS3存储桶中的所有对象。我使用以下代码段在C#5中工作:

var listRequest = new ListObjectsRequest().WithBucketName(bucketName);
ListObjectsResponse listResponse = null;
var list = new List<List<S3Object>>();

while (listResponse == null || listResponse.IsTruncated)
{
    listResponse = await Task<ListObjectsResponse>.Factory.FromAsync(
        client.BeginListObjects, client.EndListObjects, listRequest, null);

    list.Add(listResponse.S3Objects);

    if (listResponse.IsTruncated)
    {
        listRequest.Marker = listResponse.NextMarker;
    }
}

return list.SelectMany(l => l);

我正在异步调用BeginListObjects / EndListObjects对,但每次响应说它被截断时我都要重复该调用。这段代码适合我。

但是,我现在想在C#4的TPL中做到这一点,在那里我没有使用async / await的奢侈,并且想要了解是否可以使用continuation来完成。

我如何在C#4中做同样的事情?

1 个答案:

答案 0 :(得分:4)

好的,所以不是将项目放入每个任务/继续的列表中,而是在非等待模型中更容易让每个任务/继续返回整个序列。鉴于此,我使用以下辅助方法将每个迭代结果添加到总计中。

public static Task<IEnumerable<T>> Concat<T>(Task<IEnumerable<T>> first
        , Task<IEnumerable<T>> second)
{
    return Task.Factory.ContinueWhenAll(new[] { first, second }, _ =>
    {
        return first.Result.Concat(second.Result);
    });
}

接下来,我使用follow方法获取单个结果的任务并将其转换为序列的任务(仅包含该项目)。

public static Task<IEnumerable<T>> ToSequence<T>(this Task<T> task)
{
    var tcs = new TaskCompletionSource<IEnumerable<T>>();
    task.ContinueWith(_ =>
    {
        if (task.IsCanceled)
            tcs.SetCanceled();
        else if (task.IsFaulted)
            tcs.SetException(task.Exception);
        else
            tcs.SetResult(Enumerable.Repeat(task.Result, 1));
    });

    return tcs.Task;
}

请注意,您有一些字段/本地未定义;我假设您可以毫不费力地将它们添加到适当的方法中。

private Task<IEnumerable<S3Object>> method(object sender, EventArgs e)
{

    ListObjectsResponse listResponse = null;
    return Task<ListObjectsResponse>.Factory.FromAsync(
        client.BeginListObjects, client.EndListObjects, listRequest, null)
        .ToSequence()
        .ContinueWith(continuation);
}

这是真正的魔法发生的地方。基本上,

public Task<IEnumerable<S3Object>> continuation(Task<IEnumerable<S3Object>> task)
{
    if (task.Result == null)  //not quite sure what null means here//may need to edit this recursive case
    {
        return Task<ListObjectsResponse>.Factory.FromAsync(
                client.BeginListObjects, client.EndListObjects, listRequest, null)
                .ToSequence()
                .ContinueWith(continuation);
    }
    else if (task.Result.First().IsTruncated)
    {
        //if the results were trunctated then concat those results with 
        //TODO modify the request marker here; either create a new one or store the request as a field and mutate.
        Task<IEnumerable<S3Object>> nextBatch = Task<ListObjectsResponse>.Factory.FromAsync(
                client.BeginListObjects, client.EndListObjects, listRequest, null)
                .ToSequence()
                .ContinueWith(continuation);
        return Concat(nextBatch, task);//recursive continuation call
    }
    else //if we're done it means the existing results are sufficient
    {
        return task;
    }
}