如何在不停止.Net(c#)中的方法的情况下从异步方法返回值?

时间:2017-06-29 19:52:34

标签: c# .net multithreading asynchronous

考虑以下示例:

public Fruit ProcessFruitBasket(List<Fruit> fruits) {
    Fruit result = null;
    foreach (var fruit in fruits) {
        ProcessFruit(fruit);
        if (fruit.MeetsSomeCondition()) result = fruit;
    }
    return fruit;
}

方法ProcessFruitBasket()需要对列表中的每个项执行某些操作(ProcessFruit())。此外,它需要找到一个满足某些条件的特定项目并将其返回。 当前版本是单线程的,因此,调用者需要等待所有项目处理才能恢复其结果,即使它想要的项目是列表中的第一项。

我想要做的是异步运行ProcessFruitBasket(),并在找到它时返回所需的值,以便调用者可以继续查看结果。但是,ProcessFruitBasket()仍应继续处理列表的其余部分。

或者,如果我可以以某种方式启动一个任务并让它填充一个调用者有引用的变量,那么调用者可能有一种方法可以等待被填充的变量(不再是null?)但是没有等待整个任务?

我对.Net Tasks名称空间可能如何解决这个问题有一些想法,但我还没有找到任何相关的例子。可能这不是一个选择吗?我有一个笨重的解决方案,代码可以在列表中循环,直到找到正确的项目,然后在原始方法返回找到的结果时触发继续异步处理列表其余部分的任务。看来可能已经有了一个更清洁的机制。可能是一种使用我尚未找到的Parallel.ForEach()的方法吗?

重要的是调用者应该等待结果,而不必等待处理整个列表

2 个答案:

答案 0 :(得分:0)

您可以传递一项功能

public void ProcessFruitBasket(List<Fruit> fruits, Func<Fruit> callback) {
    foreach (var fruit in fruits) {
        ProcessFruit(fruit);
        if (fruit.MeetsSomeCondition()) 
            callback(fruit);
    }
}

另外注意,如果你不是await异步方法,那么.net将执行下一个语句,直到你在异步方法上调用await

更新1

    public async Task ProcessFruitBasket(IList<Fruit> fruits, Action<Fruit> callBack)
    {
        foreach (var fruit in fruits)
        {
            var process = ProcessFruit(fruit); // DO NOT await here

            // do something else here

            await process; // you have to await here since you have to check condition on processed fruit
            if (fruit.MeetsSomeCondition())
            {
                callBack(fruit);
            }
        }
    }
    public async Task ProcessFruit(Fruit fruit)
    {
        // do async actions here
        await Task.FromResult<int>(0);
    }

    public void Callback(Fruit fruit)
    {
        // Do something with the fruit
    }

答案 1 :(得分:0)

这样的事可能有用。您将需要返回代表剩余处理的内容,以便您可以在某个时刻等待它。我会使用一个对象捕获两件事:

public class FruitProcessingResult
{
    public bool FruitWasFound { get; set; }
    public Fruit FruitMeetingCondition { get; set; }
    public Task RemainingProcessing { get; set; }
}

    private async Task<FruitProcessingResult> ProcessFruitBasket(List<Fruit> fruits)
    {
        var result = new FruitProcessingResult();

        //Kick off all fruit processing
        var tasks = fruits.Select(f => ProcessAndTestFruit(f, result)).ToList();

        while (tasks.Any())
        {
            var completed = await Task.WhenAny(tasks);
            tasks.Remove(completed);

            lock (result)
            {
                if (result.FruitWasFound)
                {
                    result.RemainingProcessing = Task.WhenAll(tasks);
                    return result;
                }
            }
        }

        result.RemainingProcessing = Task.CompletedTask;
        return result;
    }

    private async Task ProcessAndTestFruit(Fruit fruit, FruitProcessingResult result)
    {          
        //Process each individual fruit async if you can, otherwise kick off a task
        await ProcessFruitAsync(fruit);
        //await Task.Run(() => ProcessFruitAsync(fruit));

        if (fruit.MeetsSomeCondition())
        {
            lock (result)
            {
                if (!result.FruitWasFound)
                {
                    result.FruitWasFound = true;
                    result.FruitMeetingCondition = fruit;
                }
            }
        }
    }