49

I have object obj which is 3rd party component,

// this could take more than 30 seconds
int result = obj.PerformInitTransaction(); 

I don't know what is happening inside. What I know is if it take longer time, it is failed.

how to setup a timeout mechanism to this operation, so that if it takes more than 30 seconds I just throw MoreThan30SecondsException ?

Chris S
  • 64,770
  • 52
  • 221
  • 239
Anwar Chandra
  • 8,538
  • 9
  • 45
  • 61

11 Answers11

81

You could run the operation in a separate thread and then put a timeout on the thread join operation:

using System.Threading;

class Program {
    static void DoSomething() {
        try {
            // your call here...
            obj.PerformInitTransaction();         
        } catch (ThreadAbortException) {
            // cleanup code, if needed...
        }
    }

    public static void Main(params string[] args) {

        Thread t = new Thread(DoSomething);
        t.Start();
        if (!t.Join(TimeSpan.FromSeconds(30))) {
            t.Abort();
            throw new Exception("More than 30 secs.");
        }
    }
}
Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193
  • 1
    @Bomboca: I rolled back your edit, the `Exception` I'm throwing should not be a `ThreadAbortException`, which is something thrown by the CLR when a call to `Abort` is made. – Paolo Tedesco Feb 18 '16 at 07:49
  • 2
    It is a blocking call, if you need the main thread do other things at this time this wont work ! – feldeOne Jan 04 '17 at 15:19
27

More simply using Task.Wait(TimeSpan):

using System.Threading.Tasks;

var task = Task.Run(() => obj.PerformInitTransaction());
if (task.Wait(TimeSpan.FromSeconds(30)))
    return task.Result;
else
    throw new Exception("Timed out");
Colonel Panic
  • 132,665
  • 89
  • 401
  • 465
  • This is very simple and available since .net 4.0. – Anwar Chandra Apr 21 '17 at 08:17
  • @anwar I'll run into this answer again and I'm using .NET Framework 4.0, so please let me correct you: Task.Run is available from .NET 4.5 onwards. [link](https://msdn.microsoft.com/en-gb/library/hh195051(v=vs.110).aspx) – Emanuele Bellini Jul 18 '18 at 11:07
  • This won't end the thread. It is suggested to use with CancellationToken. – joe Mar 27 '19 at 01:29
8

If you don't want to block the main thread you can use a System.Threading.Timer:

private Thread _thread;

void Main(string[] args)
{
    _thread = new ThreadStart(ThreadEntry);
    _thread.Start();
    Timer timer = new Timer(Timeout,null,30000,Timeout.Infinite);
}


void ThreadEntry()
{
    int result = obj.PerformInitTransaction(); 
}

void TimeOut(object state)
{
    // Abort the thread - see the comments
    _thread.Abort();

    throw new ItTimedOutException();
}

Jon Skeet has a less forceful way (Shutting Down Worker Threads Gracefully) of stopping the thread than abort.

However as you're not in control of the operations PerformInitTransaction() is doing there is not much you can do from when Abort fails and leaves the object in an invalid state. As mentioned if you are able to cleanup anything that aborting the PerformInitTransaction has left hanging, you can do this by catching the ThreadAbortException, though as it's a 3rd party call it'll mean guessing the state you've left their method in.

The PerformInitTransaction should really be the one providing the timeout.

Chris S
  • 64,770
  • 52
  • 221
  • 239
5

The following are two implementations which also throw any exception that happens in the internal task.

For actions (no return value):

public static bool DoWithTimeout(Action action, int timeout)
{
    Exception ex = null;
    CancellationTokenSource cts = new CancellationTokenSource();
    Task task = Task.Run(() =>
    {
        try
        {
            using (cts.Token.Register(Thread.CurrentThread.Abort))
            {
                action();
            }
        }
        catch (Exception e)
        {
            if (!(e is ThreadAbortException))
                ex = e;
        }
    }, cts.Token);
    bool done = task.Wait(timeout);
    if (ex != null)
        throw ex;
    if (!done)
        cts.Cancel();
    return done;
}

For Funcs (with return value):

public static bool DoWithTimeout<T>(Func<T> func, int timeout, out T result)
{
    Exception ex = null;
    result = default(T);
    T res = default(T);
    CancellationTokenSource cts = new CancellationTokenSource();
    Task task = Task.Run(() =>
    {
        try
        {
            using (cts.Token.Register(Thread.CurrentThread.Abort))
            {
                res = func();
            }
        }
        catch (Exception e)
        {
            if (!(e is ThreadAbortException))
                ex = e;
        }
    }, cts.Token);
    bool done = task.Wait(timeout);
    if (ex != null)
        throw ex;
    if (done)
        result = res;
    else
        cts.Cancel();
    return done;
}
Gilad Rave
  • 101
  • 1
  • 5
3

I think this is simplest of all:

using System.Threading.Tasks;

var timeToWait = 30000; //ms
Task.Run(async () =>
{
    await Task.Delay(timeToWait);
    //do your timed task i.e. --
    int result = obj.PerformInitTransaction(); 
});
David Rogers
  • 2,601
  • 4
  • 39
  • 84
Ariful Islam
  • 664
  • 8
  • 12
  • Why is this upvoted? It does not solve what was asked in the question. This waits for bit and then performs the operation, the OP wants to fail if the operation takes too long to complete. – jhmckimm Sep 01 '22 at 08:04
2

You need to be careful about aborting an operation like this, especially as it's in a 3rd party component that you (possibly) don't have access to the code to modify.

If you abort the operation then you won't know what state you've left the underlying class in. For example, it may have acquired a lock, and your about has caused that lock to not be released. Even if you destroy the object after aborting the operation it may have altered some state that is global to it and therefore you won't be able to reliably create a new instance without a restart.

Sean
  • 60,939
  • 11
  • 97
  • 136
1

You might look at invoking the method in a thread and upon the timeout, abort the thread and raise the exception. Also, you shall have to handle the ThreadBorted Exception in this case.

Kangkan
  • 15,267
  • 10
  • 70
  • 113
1

New approach in .NET 6 / C# 10:

var task = Task.Run(() => SomeMethod(input));
return await task.WaitAsync(TimeSpan.FromSeconds(30));
Luis Cantero
  • 1,278
  • 13
  • 11
0

There is a nice example of a generic solution to this using a helper class here.

It uses the Action delegate to avoid the Thread creation/destruction shown in the previous example.

I hope this helps.

Community
  • 1
  • 1
BrianB
  • 424
  • 2
  • 13
0

this is what I would use. works similar to how a javascript timeout works.

public class Toolz {
    public static System.Threading.Tasks.Task<object> SetTimeout(Func<object> func, int secs) {
        System.Threading.Thread.Sleep(TimeSpan.FromSeconds(secs));
        return System.Threading.Tasks.Task.Run(() => func());
    }
}

class Program {
    static void Main(string[] args) {
        Console.WriteLine(DateTime.Now);
        Toolz.SetTimeout(() => {
            Console.WriteLine(DateTime.Now);
            return "";
        }, 10);
    }

}
Mike
  • 623
  • 6
  • 26
0

I just ran into this in a .NET 4.0 app (no access to Task.Run, Task.Delay, etc.). If you will excuse the last line (which is the setTimeout part) it's fairly concise.

int sleepTime = 10000;
Action myAction = () => {
    // my awesome cross-thread update code    
    this.BackColor = Color.Red;
};
new System.Threading.Thread(() => { System.Threading.Thread.Sleep(sleepTime); if (InvokeRequired) myAction(); else myAction(); }).Start();
user875234
  • 2,399
  • 7
  • 31
  • 49