如何在c#中正确地异步执行任务列表

本文关键字:异步 执行任务 列表 正确地 | 更新日期: 2023-09-27 18:06:38

我有一个对象列表,我需要在上面运行一个长时间运行的进程,我想异步地启动它们,然后当它们全部完成时,将它们作为列表返回给调用方法。我一直在尝试我发现的不同方法,但是似乎这些进程仍然按照它们在列表中的顺序同步运行。所以我确信在如何执行任务列表的过程中我遗漏了一些东西。下面是我的代码:

public async Task<List<ShipmentOverview>> GetShipmentByStatus(ShipmentFilterModel filter)
    {
        if (string.IsNullOrEmpty(filter.Status))
        {
            throw new InvalidShipmentStatusException(filter.Status);
        }
        var lookups = GetLookups(false, Brownells.ConsolidatedShipping.Constants.ShipmentStatusType);
        var lookup = lookups.SingleOrDefault(sd => sd.Name.ToLower() == filter.Status.ToLower());
        if (lookup != null)
        {
            filter.StatusId = lookup.Id;
            var shipments = Shipments.GetShipments(filter);
            var tasks = shipments.Select(async model => await GetOverview(model)).ToList();
            ShipmentOverview[] finishedTask = await Task.WhenAll(tasks);
            return finishedTask.ToList();
        }
        else
        {
            throw new InvalidShipmentStatusException(filter.Status);
        }
    }

        private async Task<ShipmentOverview> GetOverview(ShipmentModel model)
    {
        String version;
        var user = AuthContext.GetUserSecurityModel(Identity.Token, out version) as UserSecurityModel;
        var profile = AuthContext.GetProfileSecurityModel(user.Profiles.First());
        var overview = new ShipmentOverview
        {
            Id = model.Id,
            CanView = true,
            CanClose = profile.HasFeatureAction("Shipments", "Close", "POST"),
            CanClear = profile.HasFeatureAction("Shipments", "Clear", "POST"),
            CanEdit = profile.HasFeatureAction("Shipments", "Get", "PUT"),
            ShipmentNumber = model.ShipmentNumber.ToString(),
            ShipmentName = model.Name,
        };
        var parcels = Shipments.GetParcelsInShipment(model.Id);
        overview.NumberParcels = parcels.Count;
        var orders = parcels.Select(s => WareHouseClient.GetOrderNumberFromParcelId(s.ParcelNumber)).ToList();
        overview.NumberOrders = orders.Distinct().Count();

        //check validations
        var vals = Shipments.GetShipmentValidations(model.Id);
        if (model.ValidationTypeId == Constants.OrderValidationType)
        {
            if (vals.Count > 0)
            {
                overview.NumberOrdersTotal = vals.Count();
                overview.NumberParcelsTotal = vals.Sum(s => WareHouseClient.GetParcelsPerOrder(s.ValidateReference));
            }
        }

        return overview;
    }

如何在c#中正确地异步执行任务列表

看起来你在使用异步方法,而你真正想要的是线程。

异步方法在调用async方法时将控制权交还给调用方法,然后等待直到await上的方法完成。你可以在这里看到它是如何工作的。
基本上,async/await方法的唯一用途是不锁定UI,以便它保持响应。

如果您想并行地触发多个处理,您将希望使用线程,如:

using System.Threading.Tasks;
public void MainMethod() {
    // Parallel.ForEach will automagically run the "right" number of threads in parallel
    Parallel.ForEach(shipments, shipment => ProcessShipment(shipment));
    // do something when all shipments have been processed
}
public void ProcessShipment(Shipment shipment) { ... }

将方法标记为async不会自动神奇地使其并行执行。由于您根本没有使用await,因此它实际上将完全同步执行,就好像它不是async一样。您可能在某处读到过async使函数异步执行,但这根本不是真的-忘记它吧。当你使用await时,它所做的唯一一件事就是为你构建一个状态机来处理任务的延续,并实际构建所有的代码来管理这些任务和它们的错误处理。

如果你的代码主要是I/O绑定,使用异步api与await,以确保方法实际上并行执行。如果它们是CPU限制的,Task.Run(或Parallel.ForEach)将工作得最好。

同样,做.Select(async model => await GetOverview(model)也没有意义。它几乎等同于.Select(model => GetOverview(model)。在任何情况下,由于该方法实际上不返回异步任务,因此它将在执行Select时执行,远早于执行Task.WhenAll

考虑到这一点,即使GetShipmentByStatusasync也几乎毫无用处-您只使用await来等待Task.WhenAll,但由于所有任务都已经完成,因此它将简单地同步完成。

如果您的任务是CPU限制而不是I/O限制,那么我相信您正在寻找以下模式:

static void Main(string[] args) {
    Task firstStepTask = Task.Run(() => firstStep());
    Task secondStepTask = Task.Run(() => secondStep());
    //...
    Task finalStepTask = Task.Factory.ContinueWhenAll(
    new Task[] { step1Task, step2Task }, //more if more than two steps...
    (previousTasks) => finalStep());
    finalStepTask.Wait();
}