运行多个异步任务并等待它们全部完成

时间:2014-07-29 06:34:29

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

我需要在控制台应用程序中运行多个异步任务,并在进一步处理之前等待它们全部完成。

那里有很多文章,但我读的越多越好。我已经阅读并理解了任务库的基本原理,但我明显错过了某个链接。

我知道可以将任务链接起来,以便它们在另一个完成后开始(这几乎是我读过的所有文章的场景),但我希望我的所有任务都在同时,我想知道他们一旦完成了。

对于这样的场景,最简单的实现是什么?

10 个答案:

答案 0 :(得分:362)

这两个答案都没有提及等待的Task.WhenAll

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

Task.WaitAllTask.WhenAll之间的主要区别在于前者将阻止(类似于在单个任务上使用Wait)而后者将不会并且可以等待,从而产生控制回到调用者,直到所有任务完成。

更多的是,异常处理有所不同:

<强> Task.WaitAll

  

至少有一个Task实例被取消 - 或者 - 在执行至少一个Task实例期间抛出了异常。如果任务被取消,则AggregateException在其InnerExceptions集合中包含OperationCanceledException。

<强> Task.WhenAll

  

如果任何提供的任务在故障状态下完成,则返回的任务也将在Faulted状态下完成,其异常将包含来自每个提供的任务的一组未解包的异常的聚合。

     

如果提供的任务都没有出现故障但至少其中一个被取消,则返回的任务将以“已取消”状态结束。

     

如果没有任何任务出现故障而且没有任何任务被取消,则生成的任务将以RanToCompletion状态结束。   如果提供的array / enumerable不包含任务,则返回的任务将在返回给调用者之前立即转换到RanToCompletion状态。

答案 1 :(得分:91)

您可以创建许多任务,例如:

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());

答案 2 :(得分:23)

我见过的最佳选择是以下扩展方法:

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

这样称呼:

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

或使用异步lambda:

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});

答案 3 :(得分:15)

您可以使用WhenAll,它会返回一个没有返回类型的等待TaskWaitAll,并会阻止进一步执行Thread.Sleep的代码执行,直到所有任务完成,取消或出现故障。

enter image description here

示例

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

如果你想以特定的顺序运行任务,你可以从this anwser获得灵感。

答案 4 :(得分:8)

您想链接Task,还是可以并行调用?

用于链接
做一些像

这样的事情
Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

并且不要忘记检查每个Task中的前一个ContinueWith实例,因为它可能会出现故障。

以并行方式
我遇到的最简单的方法:Parallel.Invoke 否则会Task.WaitAll或者您甚至可以使用WaitHandle来对剩余的零操作进行倒计时(等待,有新的课程:CountdownEvent),或者。 ..

答案 5 :(得分:4)

这就是我使用数组 Func&lt;&gt; 的方式:

var tasks = new Func<Task>[]
{
   () => myAsyncWork1(),
   () => myAsyncWork2(),
   () => myAsyncWork3()
};

await Task.WhenAll(tasks.Select(task => task()).ToArray()); //Async    
Task.WaitAll(tasks.Select(task => task()).ToArray()); //Or use WaitAll for Sync

答案 6 :(得分:3)

还有另一个答案...但是我通常会遇到这种情况,当我需要同时加载数据并将其放入变量中时,例如:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

答案 7 :(得分:1)

应该有比接受的答案更简洁的解决方案。同时执行多个任务并获得其结果应该不需要三个步骤。

  1. 创建任务
  2. 等待Task.WhenAll(任务)
  3. 获取任务结果(例如task1.Result)

这是将方法简化为两步的方法:

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=MyKey"></script>

<script type="text/javascript">
function initialize() {
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(showPosition, showError);
    }

    function showPosition(position) {
        var currentLatLong = position.coords;
        var mapLatLong = parseFloat(position.coords).value;

        var mapOptions = {
            zoom: 13,
            center: mapLatLong,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };

        var map = new google.maps.Map(document.getElementById('map'), mapOptions);
        var directionsService = new google.maps.DirectionsService;
        var directionsDisplay = new google.maps.DirectionsRenderer;

        addMarker(currentLatLong.latitude, currentLatLong.longitude, "You are here");

        $.ajax({
            cache: false,
            type: "POST",
            url: "@Url.Action("GetNearbyLocation")",
            dataType: "json",
            contetType: "application/json; charset=utf-8",
            data: { "latitude": currentLatLong.latitude, "longitude": currentLatLong.longitude },
            success: function (data) {
                $.each(data, function (i, item) {
                    addMarker(item.latitude, item.longitude, item.name);
                });
            },
            error: function () {
                alert("There was an error processing your request. Please try again later");
            }
        });

        function addMarker(x, y, locationName) {
            var infowindow = new google.maps.InfoWindow({
                content: locationName
            });

            var location = new google.maps.LatLng(x, y);
            var marker = new google.maps.Marker({
                position: location,
                map: map,
                title: locationName,
            });

            infowindow.open(map, marker);

            marker.addListener('click', function () {
                infowindow.open(map, marker);
                calculateAndDisplayRoute(directionsService, directionsDisplay, x, y);
            });
        }

        function calculateAndDisplayRoute(directionsService, diresctionsDisplay, x, y) {
            var latLongSource = { lat: parseFloat(currentLatLong.latitude), lng: parseFloat(currentLatLong.longitude) };
            var latLongDestination = { lat: parseFloat(x), lng: parseFloat(y) };

            directionsService.route({
                origin: latLongSource,
                destination: latLongDestination,
                travelMode: "DRIVING"
            }, function (response, status) {
                if (status == "OK") {
                    directionDisplay.setDirections(response);
                }
                else {
                    alert("Directions not available at the moment. Please try again later.");
                }
            });
        }
    }

    function showError(error) {
        if (error.code == 1) {
            alert("User denied the request for Geolocation");
        }
        else if (error.code == 2) {
            alert("Location information is unavailable");
        }
        else {
            alert("There was an error processing requesting your location");
        }
    } 

}
google.maps.event.addDomListener(window, 'load', initialize)
</script>

您可以像这样使用它:

    public async Task<Tuple<T1, T2>> WhenAllGeneric<T1, T2>(Task<T1> task1, Task<T2> task2)
    {
        await Task.WhenAll(task1, task2);
        return Tuple.Create(task1.Result, task2.Result);
    }

这消除了对临时任务变量的需要。使用此方法的问题是,尽管它可以同时执行两个任务,但您需要针对三个任务或任何其他数量的任务进行更新。如果其中一项任务未返回任何内容,它也将无法正常工作。确实,.Net库应该提供可以做到这一点的东西

答案 8 :(得分:-1)

我准备了一段代码来向您展示如何在某些情况下使用该任务。

    // method to run tasks in a parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks) {

        await Task.WhenAll(tasks);
    }
    // methode to run task one by one 
    public async Task RunMultipleTaskOneByOne(Task[] tasks)
    {
        for (int i = 0; i < tasks.Length - 1; i++)
            await tasks[i];
    }
    // method to run i task in parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks, int i)
    {
        var countTask = tasks.Length;
        var remainTasks = 0;
        do
        {
            int toTake = (countTask < i) ? countTask : i;
            var limitedTasks = tasks.Skip(remainTasks)
                                    .Take(toTake);
            remainTasks += toTake;
            await RunMultipleTaskParallel(limitedTasks.ToArray());
        } while (remainTasks < countTask);
    }

答案 9 :(得分:-1)

如果您使用 async/await pattern,您可以像这样并行运行多个任务:

?ga