async foreach with breaking condition

时间:2013-10-19 08:51:44

标签: c# .net asynchronous .net-4.5 system.reactive

我有一个界面:

public interface IRunner
{
    Task<bool> Run(object o);
}

我有一些使用async实现它的类,例如:

public class Runner1 : IRunner
{
    public async Task<bool> Run(object o)
    {
        var num  = await SomeExternalAsyncFunc(o);
        return num < 12;
    }
}

我需要实现一个并行运行在所有Runners类上的函数,并且只有当它们全部返回true时才返回true。 在阅读thisthis之后,我想出了以下实现:

public class RunMannager
{
    public async Task<bool> Run(ConcurrentBag<IRunner> runnersBag, object o)
    {
        var results = new ConcurrentBag<bool>();
        var tasks = runnersBag.Select(async runner => results.Add(await runner.Run(o)));
        await Task.WhenAll(tasks);
        return results.All(result => result);
     }
}

但是,我对此实现有两个问题:

  • 我希望如果其中一个参赛者已经返回假,那么该功能不应该等待所有其他参赛者。

  • 有些跑步者可能永远不会回来,我想使用超时。如果跑步者在10秒钟内没有返回任何东西,则会被视为真实归还。

使用反应式扩展可能会有所帮助吗?

2 个答案:

答案 0 :(得分:2)

这是一个包含Rx的版本,包括超时。编写的代码将在LINQPad中运行,并添加了Rx引用。您可以尝试在RunnerFactory方法中构建TrueRunner,FalseRunner和SlowRunner实例。

主要观点:

  • 使用SelectToObservable()启动异步任务并将其转换为IObservable(请参阅下面的更好选择)
  • 使用Timeout()为每项任务添加超时,如果任务超时,则会根据请求替换真实结果。
  • 使用Where过滤掉True结果 - 我们现在只收到有关错误结果的通知。
  • 如果流中有任何元素,则
  • Any返回true,否则返回false,因此在随后的Select中翻转此结果,我们就完成了。

代码:

void Main()
{
    Observable.Merge(
        RunnerFactory().Select(x => x.Run(null).ToObservable()
        .Timeout(TimeSpan.FromSeconds(1), Observable.Return(true))))
        .Where(res => !res)
        .Any().Select(res => !res)
        .Subscribe(
            res => Console.WriteLine("Result: " + res),
            ex => Console.WriteLine("Error: " + ex.Message));                                           
}

public IEnumerable<IRunner> RunnerFactory()
{
    yield return new FalseRunner();
    yield return new SlowRunner();
    yield return new TrueRunner();
}

public interface IRunner
{
    Task<bool> Run(object o);
}

public class Runner : IRunner
{
    protected bool _outcome;

    public Runner(bool outcome)
    {
        _outcome = outcome;
    }

    public virtual async Task<bool> Run(object o)
    {
        var result = await Task<bool>.Factory.StartNew(() => _outcome);     
        return result;
    }
}

public class TrueRunner : Runner
{
    public TrueRunner() : base(true) {}
}   

public class FalseRunner : Runner
{
    public FalseRunner() : base(false) {}
}   

public class SlowRunner : Runner
{
    public SlowRunner() : base(false) {}

    public override async Task<bool> Run(object o)
    {
        var result = await Task<bool>.Factory.StartNew(
            () => { Thread.Sleep(5000); return _outcome; });        
        return result;
    }
}   

根据我使用的Runner实现,OnError处理程序是多余的;如果你想在实现中抑制Runner错误,你可能想要考虑一个Catch - 你可以像对待Timeout一样替换IObservable<bool>

编辑我认为值得一提的另一件事是,使用Observable.StartAsync是启动任务的更好方法,并且也会为您提供取消支持。以下是一些修改后的代码,显示了SlowRunner如何支持取消。令牌由StartAsync传入,如果处置订阅,则会导致取消。如果Any检测到元素,这一切都会透明地发生。

void Main()
{
    var runners = GetRunners();     

    Observable.Merge(runners.Select(r => Observable.StartAsync(ct => r.Run(ct, null))
                    .Timeout(TimeSpan.FromSeconds(10), Observable.Return(true))))
                    .Where(res => !res)
                    .Any().Select(res => !res)
                    .Subscribe(
                        res => Console.WriteLine("Result: " + res));
}

public static IEnumerable<IRunner> GetRunners()
{
    yield return new Runner(false);
    yield return new SlowRunner(true);
}

public interface IRunner
{
    Task<bool> Run(CancellationToken ct, object o);
}

public class Runner : IRunner
{
    protected bool _outcome;

    public Runner(bool outcome)
    {
        _outcome = outcome;
    }

    public async virtual Task<bool> Run(CancellationToken ct, object o)
    {
        var result = await Task<bool>.Factory.StartNew(() => _outcome);
        return result;
    }
}

public class SlowRunner : Runner
{
    public SlowRunner(bool outcome) : base(outcome)
    {
    }

    public async override Task<bool> Run(CancellationToken ct, object o)
    {
        var result = await Task<bool>.Factory.StartNew(() => 
        {
            for(int i=0; i<5; i++)
            {
                if(ct.IsCancellationRequested)
                {
                    Console.WriteLine("Cancelled");                     
                }
                ct.ThrowIfCancellationRequested();
                Thread.Sleep(1000);
            };
            return _outcome;
        });
        return result;
    }
}

答案 1 :(得分:0)

如何使用Parallel.ForEach()? 以下代码应该让您了解我的意思。

您可以定义CancellationTokenSource

CancellationTokenSource cancellationToken = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cancellationToken.Token;

然后将po传递给Parallel.ForEach

Parallel.ForEach(items, po, item =>
{
   //...
   if(num < 12)
     cancellationToken.Cancel(false);

});

return !cancellationToken.IsCancellationRequested;