我有一个界面:
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。 在阅读this和this之后,我想出了以下实现:
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秒钟内没有返回任何东西,则会被视为真实归还。
使用反应式扩展可能会有所帮助吗?
答案 0 :(得分:2)
这是一个包含Rx的版本,包括超时。编写的代码将在LINQPad中运行,并添加了Rx引用。您可以尝试在RunnerFactory方法中构建TrueRunner,FalseRunner和SlowRunner实例。
主要观点:
Select
和ToObservable()
启动异步任务并将其转换为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;