-2

I want to have a code block, which should be executed with a maximum time limit. If the functions hangs, it should be aborted. From this question I adapted the following solution:

public static void ExecuteWithTimeLimit(int timeLimit_milliseconds, Func<bool> codeBlock)
{
    Task task = Task.Factory.StartNew(() =>
    {
        codeBlock();
    });

    task.Wait(timeLimit_milliseconds);
}

This works as I want it to behave: If the code codeBlock hangs and takes to long, the task is aborted.

However, I want the Task to have a return value so I can use task.Result. If I implement this into the code, it doesn't work any more. In fact, the task is not cancled and the GUI freezes completly.

public static void ExecuteWithTimeLimit(int timeLimit_milliseconds, Func<bool> codeBlock)
{
    Task<bool> task = Task<bool>.Factory.StartNew(() =>
    {
        return codeBlock();
    });

    task.Wait(timeLimit_milliseconds);
}

What is the correct way to execute Methods with a return value with a maximum time limit?

Pixel_95
  • 954
  • 2
  • 9
  • 21
  • `Task.Run` is the newer api that automatically unwraps the inner Task. And you have to be careful that you don't block the current thread. It's always better to use `await` to wait for Tasks. If you don't want to do that then wait inside a new thread. – Charles Dec 31 '21 at 00:10
  • Could you include a minimal example that reproduces the deadlock behavior? – Theodor Zoulias Dec 31 '21 at 00:24
  • 1
    You need to keep in mind that using `task.Wait(...)` doesn't abort the task if the timeout is reached. It simply stops waiting. That task will always run to completion. – Enigmativity Dec 31 '21 at 01:34

3 Answers3

-1

I would recommend creating a task method and using await. This will release the thread so application doesn't lock up, and once result is available it will jump back into that thread Here is an example:

public async Task MyMethodAsync()
{
    Task<string> longRunningTask = LongRunningOperationAsync();
    // independent work which doesn't need the result of LongRunningOperationAsync can be done here

    //and now we call await on the task 
    string result = await longRunningTask;
    //use the result 
    Console.WriteLine(result);
}

public async Task<string> LongRunningOperationAsync() // assume we return an int from this long running operation 
{
    //Perform your task in here
    await Task.Delay(5000); // 5 second delay to show how it releases thread
    return "Task Complete";
}
mathis1337
  • 1,426
  • 8
  • 13
  • The question asks about specifically adding a time limit - you'd probably want to show some sort of usage of cancellation tokens which are the recommended mechanism for that these days. – Luke Briggs Dec 31 '21 at 00:22
-1

There's a lot of mucking around with cancellation tokens with tasks. I'd suggest making your life easier and use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:

public static async Task<bool> ExecuteWithTimeLimit(TimeSpan timeLimit, Func<bool> codeBlock)
    => await Observable.Amb(
        Observable.Timer(timeLimit).Select(_ => false),
        Observable.Start(() => codeBlock()));

Observable.Amb takes 2 or more observables and only returns values from whichever observable fires first. Observable.Timer fires a single value after the TimeSpan provided. Observable.Start executes what ever code and returns a single value that is the result of that code.

Effectively Amb is a race between the timer and the code.

Now I can run it like this:

Task<bool> task =
    ExecuteWithTimeLimit(TimeSpan.FromSeconds(1.0), () =>
    {
        Console.WriteLine("!");
        Thread.Sleep(TimeSpan.FromSeconds(2.0));
        Console.WriteLine("!!");
        return true;
    });
    
task.Wait();

Console.WriteLine(task.Result);

When I run that I get this on the console:

!
False
!!

If I change the timeLimit to TimeSpan.FromSeconds(3.0) then I get this:

!
!!
True
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
-1

Actually I found a solution by canceling the task after the time limit:

public static void ExecuteWithTimeLimit(int timeLimit_milliseconds, Func<bool> codeBlock)
{
    var cancellationTokenSource = new CancellationTokenSource();
    var cancellationToken = cancellationTokenSource.Token;
    Task<bool> task = Task<bool>.Factory.StartNew(() =>
    {
        try
        {
            return codeBlock();
        }
        catch (Exception e)
        {
            MessageBox.Show(e.Message, "Exeption", MessageBoxButton.OK, MessageBoxImage.Error);
            return false;
        }
    }, cancellationToken);

    task.Wait(timeLimit_milliseconds);
    cancellationTokenSource.Cancel();
}
Pixel_95
  • 954
  • 2
  • 9
  • 21