How to wait tasks with conditions?

2019-01-29 06:15发布

问题:

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 :(

回答1:

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.



回答2:

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.



回答3:

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