0

If I have a method where I want to perform some (potentially) long-running function and I want to put a limit on its execution time, I've been using this pattern (please pardon any errors in the code, typed by hand, not in an IDE, this is a simplification of a larger piece of code).

public string GetHello()
{
    var task = Task.Run(() => 
    {
        // do something long running
        return "Hello";
    });

    bool success = task.Wait(TimeSpan.FromMilliseconds(1000));

    if (success)
    {
        return task.Result;
    }
    else
    {
        throw new TimeoutException("Timed out.");
    }
}

If I want to use the GetHello method in an async capacity, i.e. public async Task<string> GetHello(), how would I do this while hopefully preserving a similar pattern? I have the following, but I get compiler warnings about This async method lacks 'await' operators and will run synchronously as expected.

public async Task<string> GetHello()
{
    var task = Task.Run(async () => 
    {
        // await something long running
        return "Hello";
    });

    bool success = task.Wait(TimeSpan.FromMilliseconds(1000));

    if (success)
    {
        return task.Result;
    }
    else
    {
        throw new TimeoutException("Timed out.");
    }
}

I just don't know how to change this or where I would put await in order for this to work as expected.

joelc
  • 2,687
  • 5
  • 40
  • 60
  • You can add a `CancellationTokenSource`. Read the docs [Task.Run](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=netcore-3.1) :: CancellationTokenSource(Int32) Initializes a new instance of the CancellationTokenSource class that will be canceled after the specified delay in milliseconds. – Barns Jun 29 '20 at 21:37
  • I know how CancellationTokenSource works - how is that relevant to answering my question? – joelc Jun 29 '20 at 21:39
  • Guru Stron implemented the `CancellationTokenSource` as I mentioned. That is how it is relevant – Barns Jun 29 '20 at 21:43
  • Throwing that `TimeoutException` won't necessarily stop the `task` from continuing to run. Is that what you had in mind? – John Wu Jun 29 '20 at 21:44
  • @Barns pointing me to the docs isn't providing an answer, but thanks anyway. – joelc Jun 29 '20 at 21:53
  • Thanks @John Wu for pointing that out, I will make sure that's addressed. – joelc Jun 29 '20 at 21:53
  • Possible duplicate of [Asynchronously wait for `Task` to complete with timeout](https://stackoverflow.com/a/11191070/2791540) – John Wu Jun 29 '20 at 21:57

1 Answers1

1

You can combine using CancellationToken and awaitable Task.WhenAny to achieve desired behavior:

public async Task<string> GetHello()
{
    var cts = new CancellationTokenSource();
    var task = Task.Run(async () =>
    {
        // await something long running and pass/use cts.Token here too
        return "Hello";
    }, cts.Token);

    var delay = Task.Delay(1000, cts.Token);

    var finishedFirst = await Task.WhenAny(task, delay);
    cts.Cancel();
    if (finishedFirst == task)
    {
        return task.Result;
    }
    else
    {
        throw new TimeoutException("Timed out.");
    }
} 
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • 1
    Probably more important is that the async method checks the token and aborts if it's been cancelled. Otherwise the long running work may still run *long* after the timeout exception is thrown – pinkfloydx33 Jun 29 '20 at 21:49
  • So add ```cts.Cancel()``` in the ```else``` block? – joelc Jun 29 '20 at 21:54
  • 2
    @joelc not in the `else` block, to the `await something long running`. – Guru Stron Jun 29 '20 at 21:56