简体   繁体   English

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

[英]How can I return a value from an asynchronous method without stopping the method in .Net (c#)?

Consider the following example: 考虑以下示例:

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

The method ProcessFruitBasket() needs to do something (ProcessFruit()) to every item in the list. 方法ProcessFruitBasket()需要对列表中的每个项目执行某些操作(ProcessFruit())。 Additionally, it needs to find one particular item that meets some condition and return it. 此外,它需要找到一个满足某些条件的特定项目并将其退回。 The current version is single-threaded, and as a result, the caller will need to wait until all of the items are processed before it can get back its result, even if the one it wants is the first item in the list. 当前版本是单线程的,因此,调用方将需要等到所有项目都处理完毕后才能返回其结果,即使它想要的是列表中的第一项。

What I would like to do is run ProcessFruitBasket() asynchronously, and have it return the desired value when it finds it so the caller can continue on with the result. 我想做的是异步运行ProcessFruitBasket(),并在找到它时返回所需的值,以便调用者可以继续执行结果。 However, the ProcessFruitBasket() should still continue to process the rest of the list. 但是,ProcessFruitBasket()应该仍然继续处理列表的其余部分。

Alternatively, if I could somehow start a Task and have it fill a variable the caller has a reference to, the maybe there is a way that the caller can await the variable being populated (not null anymore?) but not await the whole task? 或者,如果我可以某种方式启动Task并填充调用者引用的变量,也许有一种方法可以使调用者等待正在填充的变量(不再为null?)而不等待整个任务?

I have some ideas of how the .Net Tasks namespace might potentially have solved this, but I haven't been able to find any relevant examples. 我对.Net Tasks命名空间可能如何解决了这个问题有一些想法,但是我找不到任何相关示例。 It's possible this isn't an option? 这可能不是一个选择吗? I have a clunky solution in mind where the code could loop through the list until it found the right item, and then fire off a task that continues to process the rest of the list asynchronously while the original method returns the found result. 我想到一个笨拙的解决方案,其中代码可以遍历列表直到找到正确的项目,然后触发一个任务,该任务继续异步处理列表的其余部分,而原始方法返回找到的结果。 It seems like there could be a cleaner mechanism already in place for this. 似乎已经有一种更清洁的机制可以解决此问题。 Possibly a way of using Parallel.ForEach() that I haven't found yet? 可能是使用尚未找到的Parallel.ForEach()的一种方法吗?

The important part is that the caller should await the result without having to await the processing of the entire list . 重要的是呼叫者应等待结果而不必等待整个列表的处理。

You can pass a function 您可以传递功能

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

also on side note that if you dont await asynchronous method then .net will execute next statements until you call await on asynchronous method. 另请注意,如果您不await异步方法,则.net将执行next语句,直到您对异步方法调用await为止。

Update 1 更新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
    }

Something like this might work. 这样的事情可能会起作用。 You will want to get back something representing the remaining processing so that you can await it at some point. 您将需要取回代表剩余处理的内容,以便您可以在某个时候等待它。 I would use an object to capture both things: 我将使用一个对象来捕获这两件事:

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;
                }
            }
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM