C# task factory timeout

2020-02-05 03:23发布

问题:

I have to execute a long process operation in a thread and continue by returning the result to a function. Here is my code :

Task<ProductEventArgs>.Factory.StartNew(() =>
    {
        try
        {
             // long operation which return new ProductEventArgs with a list of product

        }
        catch (Exception e)
        {
            return new ProductEventArgs() { E = e };
        }

    }).ContinueWith((x) => handleResult(x.Result), TaskScheduler.FromCurrentSynchronizationContext());

The problem is actually I don't have a timeout. I want to put a timer in order to return something like this :

   new ProductEventArgs() { E = new Exception("timeout") }; 

if the timeout is reached. Can't use await/async. Thanks a lot !

回答1:

This code does what you have expressed here:

var timeout = TimeSpan.FromSeconds(5);

var actualTask = new Task<ProductEventArgs>(() =>
{
    var longRunningTask = new Task<ProductEventArgs>(() =>
    {
        try
        {
            Thread.Sleep(TimeSpan.FromSeconds(10)); // simulates the long running computation
            return new ProductEventArgs();
        }
        catch (Exception e)
        {
            return new ProductEventArgs() { E = e };
        }
    }, TaskCreationOptions.LongRunning);

    longRunningTask.Start();

    if (longRunningTask.Wait(timeout)) return longRunningTask.Result;

    return new ProductEventArgs() { E = new Exception("timed out") };
});

actualTask.Start();

actualTask.Wait();

Console.WriteLine("{0}", actualTask.Result.E); // handling E

As you see longRunningTask is created with TaskCreationOptions.LongRunning option. That way it will have a dedicated Thread for it's execution and does not interfere with normal behavior of ThreadPool by occupying a thread from there for too long - which will be needed for other thing like i.e. UI. That's important for long running tasks.

Note: You could then handle actualTask with ContinueWith but I wanted to express the essence here.



回答2:

You should use CancellationTokens:

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var token = cts.Token;
Task<ProductEventArgs>.Factory.StartNew(() =>
{
    try
    {
        // occasionally, execute this line:
        token.ThrowIfCancellationRequested();
    }
    catch (OperationCanceledException)
    {
        return new ProductEventArgs() { E = new Exception("timeout") };
    }
    catch (Exception e)
    {
        return new ProductEventArgs() { E = e };
    }

}).ContinueWith((x) => handleResult(x.Result), TaskScheduler.FromCurrentSynchronizationContext());


回答3:

You may use returned task object for StartNew method and then user Wait method to determine timeout.

Task<ProductEventArgs> task = Task<ProductEventArgs>.Factory.StartNew(() => {...});
if (!Task.Wait(new TimeSpan(0,0,1,0)) // wait for 1 minute
{
   // throw exception or something else if timeout
}


回答4:

You can run a Task.Delay(timeout) task in parallel and check what task was first to complete (Task.WhenAny() is very handy in this case):

public void FetchProduct(TimeSpan timeout)
{
    var fetchTask = Task<ProductEventArgs>.Factory.StartNew(
        () =>
        {
            try
            {
                // long operation which return new ProductEventArgs with a list of product
            }
            catch(Exception e)
            {
                return new ProductEventArgs() { E = e };
            }
        });
    Task<ProductEventArgs> resultTask;
    if(timeout != Timeout.InfiniteTimeSpan)
    {
        var timeoutTask = Task.Delay(timeout);
        resultTask = Task.WhenAny(resultTask, timeoutTask).ContinueWith<ProductEventArgs>(
            t =>
            {
                // completed task is the result of WhenAny
                if(t.Result == fetchTask)
                {
                    return fetchTask.Result;
                }
                else
                {
                    return new ProductEventArgs() { E = new TimeoutException() };
                }
            });
    }
    else
    {
        resultTask = fetchTask;
    }
    resultTask.ContinueWith(x => handleResult(x.Result), TaskScheduler.FromCurrentSynchronizationContext());
}

Note that this solution doesn't have any cancellation logic, and your long running task will be still running even if it times out.



回答5:

Just start another task within the main task (surrogate):

Task.Factory.StartNew(() => 
        {
            // returns a string result
            var tsk = new Task<string>(() => { return VeryImportantThingsToDo(); });
            try
            {
                tsk.Start();
                if (!tsk.Wait(5000))
                    throw new TimeoutException();
                return tsk.Result;
            }
            catch (TimeoutException)
            {
                // Jabba Dabba Doooooooohhhhhh
            }

            return "<unknown>";
        }).ContinueWith((o) => string result = o.Result));