frenchie
frenchie

Reputation: 51947

Running two tasks with Parallel.Invoke and add a timeout in case one task takes longer

I'm calling two functions that rely on some external web services. For now, they run in parallel and when they both complete the execution resumes. However, if the external servers take too much time to process the requests, it could lock my code for a while.

I want to add a timeout so that if the servers take more than 10 seconds to respond then just continue on. This is what I have, how can I add a timeout?

Parallel.Invoke(

    () => FunctionThatCallsServer1(TheParameter),
    () => FunctionThatCallsServer2(TheParameter)            
);

RunThisFunctionNoMatterWhatAfter10Seconds();

Upvotes: 4

Views: 1372

Answers (3)

Rich N
Rich N

Reputation: 9475

I don't think there's an easy way of timing out a Parallel.Invoke once the functions have started, which clearly they will have done after ten seconds here. Parallel.Invoke waits for the functions to complete even if you cancel, so you would have to find a way to complete the functions early.

However, under the covers Parallel.Invoke uses Tasks, and if you use Tasks directly instead of Parallel.Invoke then you can provide a timeout. The code below shows how:

Task task1 = Task.Run(() => FunctionThatCallsServer1(TheParameter));
Task task2 = Task.Run(() => FunctionThatCallsServer2(TheParameter));
// 10000 is timeout in ms, allTasksCompleted is true if they completed, false if timed out
bool allTasksCompleted = Task.WaitAll(new[] { task1, task2 }, 10000);
RunThisFunctionNoMatterWhatAfter10Seconds();

One slight difference this code has with Parallel.Invoke is that if you have a VERY large number of functions then Parallel.Invoke will manage the Task creation better than just blindly creating a Task for every function as here. Parallel.Invoke will create a limited number of Tasks and re-use them as the functions complete. This won't be an issue with just a few functions to call as above.

Upvotes: 2

Ackdari
Ackdari

Reputation: 3498

You will need to create an instance of CancellationTokenSource and right at creating time you ca configure your timeout time, like

var cts = new CancellationTokenSource(timeout);

then you will need to create an instance of ParallelOptions where you set the ParallelOptions.CancellationToken to the token of the CancellationTokenSource, like

var options = new ParallelOptions {
    CancellationToken = cts.Token,
};

Then you can call Parallel.Invoke with the options and your actions

try 
{
    Parallel.Invoke(
        options,
        () => FunctionThatCallsServer1(token),
        () => FunctionThatCallsServer2(token)            
    );
}
catch (OperationCanceledException ex)
{
    // timeout reached
    Console.WriteLine("Timeout");
    throw;
}

but you will also need to hand the token to the called Server functions and handle the timeout in these actions aswell.

This is because the Parallel.Invoke will only check before it starts an action if the token it got is cancelled. That means if all actions are started before the timeout occures the Parallel.Invoke call will block as long as the actions need to finish.

Update:

A good way to test the cancellation is to define FunctionThatCallsServer1 like,

static void FunctionThatCallsServer1(CancellationToken token) {
    var endTime = DateTime.Now.AddSeconds(5);

    while (DateTime.Now < endTime) {
        token.ThrowIfCancellationRequested();

        Thread.Sleep(1);
    }
}

Upvotes: 2

Vikas Garg
Vikas Garg

Reputation: 200

Below is the code:

using System;
using System.Threading.Tasks;
namespace Algorithums
{
public class Program
{
    public static void Main(string[] args)
    {
        ParelleTasks();
        Console.WriteLine("Main");
        Console.ReadLine();
    }

    private static void ParelleTasks()
    {
        Task t = Task.Run(() => {
            FunctionThatCallsServers();
            Console.WriteLine("Task ended after 20 Seconds");
        });

        try
        {
            Console.WriteLine("About to wait for 10 sec completion of task {0}", t.Id);
            bool result = t.Wait(10000);
            Console.WriteLine("Wait completed normally: {0}", result);
            Console.WriteLine("The task status:  {0:G}", t.Status);
        }
        catch (OperationCanceledException e)
        {
            Console.WriteLine("Error: " + e.ToString());
           
        }

        RunThisFunctionNoMatterWhatAfter10Seconds();
    }


    private static bool FunctionThatCallsServers()
    {
        Parallel.Invoke(
                         () => FunctionThatCallsServer1(),
                         () => FunctionThatCallsServer2()
                    );
        return true;
    }

    private static void FunctionThatCallsServer1()
    {
        System.Threading.Thread.Sleep(20000);
        Console.WriteLine("FunctionThatCallsServer1");
    }

    private static void FunctionThatCallsServer2()
    {
        System.Threading.Thread.Sleep(20000);
        Console.WriteLine("FunctionThatCallsServer2");
    }

    private static void RunThisFunctionNoMatterWhatAfter10Seconds()
    {
        Console.WriteLine("RunThisFunctionNoMatterWhatAfter10Seconds");
    }
}


}

Upvotes: -1

Related Questions