How to wait tasks with conditions?

2019-01-29 06:40发布

I have some tasks which return a bool. I just want wait until when any task returns True firstly. is it possible ?

My first idea was to use a CancellationTokenSource but it was not a good idea because of it throws an exception when I call the Task.WaitAll method.

The second option is to use a bool I pass in reference and if it's true, return directly. it works but it's not performant :

bool isFound = false;
Task<bool> t0 = Task.Factory.StartNew<bool>(() => Find(paramA, paramB, ref isFound));
Task<bool> t1 = Task.Factory.StartNew<bool>(() => Find(paramC, paramD, ref isFound));
Task<bool> t2 = Task.Factory.StartNew<bool>(() => Find(paramE, paramF, ref isFound));
Task<bool> t3 = Task.Factory.StartNew<bool>(() => Find(paramG, paramH, ref isFound));

Task.WaitAll(new Task[] { t0, t1, t2, t3, t4 });
return t0.Result | t1.Result | t2.Result | t3.Result | t4.Result;

and in the method :

 private static bool Find(int[,] m1, int[,] m2, ref bool isFound)
 {
      if (isFound)
           return false;

      // Do work...
 }

EDIT :

As the answer preconized, I use a TaskCompletionSource<bool> now :

TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
Task<bool> t0 = Task.Factory.StartNew<bool>(() => Find(paramA, paramB);
Task<bool> t1 = Task.Factory.StartNew<bool>(() => Find(paramC, paramD);
Task<bool> t2 = Task.Factory.StartNew<bool>(() => Find(paramE, paramF);
Task<bool> t3 = Task.Factory.StartNew<bool>(() => Find(paramG, paramH);

t0.ContinueWith(_ =>
{
    if (t0.Result)
        tcs.TrySetResult(t0.Result);
});

t1.ContinueWith(_ =>
{
    if (t1.Result)
        tcs.TrySetResult(t1.Result);
});

t2.ContinueWith(_ =>
{
    if (t2.Result)
        tcs.TrySetResult(t2.Result);
});

t3.ContinueWith(_ =>
{
    if (t3.Result)
        tcs.TrySetResult(t3.Result);
});

t4.ContinueWith(_ =>
{
    if (t4.Result)
        tcs.TrySetResult(t4.Result);
});

tcs.Task.Wait();
return tcs.Task.Result;

In this case, when all tasks return false, nothing is noticed and it's normal. However I don't know how to use the WhenAll method. I tried to add this :

tcs.Task.Wait();
Task tr = Task.WhenAll(new Task[] { t0, t1, t2, t3, t4 });

if (tr.IsCompleted)
    return false;
else
    return tcs.Task.Result;

But it does not work :(

3条回答
姐就是有狂的资本
2楼-- · 2019-01-29 06:56

You could use a ManualResetEvent which would pretty much make your isFound property redundant e.g.

private static ManualResetEvent found = new ManualResetEvent(false);
...

Task.Factory.StartNew<bool>(() => Find(paramA, paramB));     
Task.Factory.StartNew<bool>(() => Find(paramC, paramD));     
Task.Factory.StartNew<bool>(() => Find(paramE, paramF));     
Task.Factory.StartNew<bool>(() => Find(paramG, paramH));  

var result = found.WaitOne(TimeSpan.FromSeconds(10)); // wait with timeout of 10 secs

// do something with result

...
private static bool Find(int[,] m1, int[,] m2)          
{          
    if (found.WaitOne(0)) // check whether MSE has already been set
        return false;        

    // Do work...
    found.Set();
}
查看更多
闹够了就滚
3楼-- · 2019-01-29 07:07

One option would be to create a TaskCompletionSource of whatever type you want (to identify the result, if that's required). Then add a continuation to each of the tasks, calling TaskCompletionSource.TrySetResult if the result is true.

Then just wait on the TaskCompletionSource.Task. It avoids you having to call Task.WaitAny repeatedly, checking for results etc.

The tricky bit is noticing when all tasks have returned false... in .NET 4.5 this would be reasonably easy, by creating another task via Task.WhenAll - then you'd just wait for the first of { success, all failed } to complete.

查看更多
手持菜刀,她持情操
4楼-- · 2019-01-29 07:11

You need WaitHandle.WaitAny. At the very beginning you set up a WaitHandle[], each one waits a Task, when the task is successfully executed (and get true as a result as you expected), you signal the corresponding WaitHandle.

查看更多
登录 后发表回答