4

I need to stop the execution of a method if it has not completed within a limited period of time.

To do this work I can use the Thread.Abort method in this way:

void RunWithTimeout(ThreadStart entryPoint, int timeout)
{
    var thread = new Thread(() =>
    {
        try
        {
            entryPoint();
        }
        catch (ThreadAbortException)
        {   }

    }) { IsBackground = true };

    thread.Start();

    if (!thread.Join(timeout))
        thread.Abort();
}

Given that I'm using .NET 3.5, is there a better way?

Edit: following the comments here my entryPoint, but I'm looking for a good way for any entryPoint.

void entryPoint()
{
   // I can't use ReceiveTimeout property
   // there is not a ReceiveTimeout for the Compact Framework
   socket.Receive(...);
}
Nick
  • 10,309
  • 21
  • 97
  • 201
  • Do you want to throw a ThnreadAbortException without using Thread.Abort, or do you want to stop the execution of a method after a timeout? – dtb Nov 07 '12 at 22:30
  • @dtb the second: stop the execution of a method after a timeout – Nick Nov 07 '12 at 22:32
  • 1
    http://stackoverflow.com/questions/299198/implement-c-sharp-generic-timeout – Andrei Schneider Nov 07 '12 at 22:33
  • 1
    Consider using a Task from the Task Parallel Library with a CancellationToken. –  Nov 07 '12 at 22:37
  • @AndreiSchneider The answer is worse than my solution... – Nick Nov 07 '12 at 22:37
  • @jdv-JandeVaan I can't use task, edit my question (.NET framework 3.5) – Nick Nov 07 '12 at 22:38
  • Why don't use the Async version? – avishayp Nov 08 '12 at 11:58
  • @avip how to completely stop the async version after the timeout? – Nick Nov 08 '12 at 12:01
  • I think Close()/Dispose() are best bet - but in that case you might as well do it with the synchronous method. Anyhow I need to test it. – avishayp Nov 08 '12 at 12:10
  • Avoiding Thread.Abort is always a good idea. Avoiding it on a thread you did not create is even better. How To Stop a Thread in .NET (and Why Thread.Abort is Evil) http://www.interact-sw.co.uk/iangblog/2004/11/12/cancellation Dangers of Thread.Abort by Eric Lippert http://blogs.msdn.com/b/ericlippert/archive/2010/02/22/should-i-specify-a-timeout.aspx – Kiquenet Apr 15 '13 at 08:38

2 Answers2

9

Answer depends on 'the work'. If work is something that can be safely stopped (i.e. not some I/O blocking operation) - use Backgroundworker.CancelAsync(...)

If you do have to cut hard - I'd consider using a Process, in which case the Aborting process is cleaner - and process.WaitForExit(timeout) is your friend.

Suggested TPL is great but unfortunately does not exist in .Net 3.5.

EDIT: You can use Reactive Extensions to follow Jan de Vaan's suggestion.

Here is my 'action timeout' snip - it's mainly here for others to comment on:

    public static bool WaitforExit(this Action act, int timeout)
    {
        var cts = new CancellationTokenSource();
        var task = Task.Factory.StartNew(act, cts.Token);
        if (Task.WaitAny(new[] { task }, TimeSpan.FromMilliseconds(timeout)) < 0)
        { // timeout
            cts.Cancel();
            return false;
        }
        else if (task.Exception != null)
        { // exception
            cts.Cancel();
            throw task.Exception;
        }
        return true;
    }

EDIT: Apparently this isn't exactly what OP wanted. Here's my attempt to devise a 'cancelable' socket receiver:

public static class Ext
{
    public static object RunWithTimeout<T>(Func<T,object> act, int timeout, T obj) where T : IDisposable
    {
        object result = null;
        Thread thread = new Thread(() => { 
            try { result = act(obj); }
            catch {}    // this is where we end after timeout...
        });

        thread.Start();
        if (!thread.Join(timeout))
        {
            obj.Dispose();
            thread.Join();
        }
        return result;
    }       
}

class Test
{
    public void SocketTimeout(int timeout)
    {
        using (var sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
        {
            Object res = Ext.RunWithTimeout(EntryPoint, timeout, sock);
        }
    }

    private object EntryPoint(Socket sock)
    {
        var buf = new byte[256];
        sock.Receive(buf);
        return buf;
    }
}
Community
  • 1
  • 1
avishayp
  • 706
  • 5
  • 9
  • 1
    CancelAsync doesn't actually abort your thread or anything like that. It sends a message to the worker thread that work should be cancelled via BackgroundWorker.CancellationPending. Your DoWork delegate that is being ran in the background must periodically check this property and handle the cancellation itself. So, it's not what I neeed. The second option (create a process) is a cleaner solution, but requires many additional resources. Reactive Extension is fine for hobby project, but I need a solution for a commercial project (I can't update to .NET 4.0). Anyway +1, it's a good answer :) – Nick Nov 08 '12 at 09:13
  • Yap. Can we see the actual 'DoWork' ? – avishayp Nov 08 '12 at 09:55
  • See edit, but (as I wrote) i prefer a good way for any entryPoint. – Nick Nov 08 '12 at 10:08
  • Avoiding Thread.Abort is always a good idea. Avoiding it on a thread you did not create is even better. How To Stop a Thread in .NET (and Why Thread.Abort is Evil) http://www.interact-sw.co.uk/iangblog/2004/11/12/cancellation Dangers of Thread.Abort by Eric Lippert http://blogs.msdn.com/b/ericlippert/archive/2010/02/22/should-i-specify-a-timeout.aspx – Kiquenet Apr 15 '13 at 08:13
4

Thread.Abort is usually a bad solution. You should use a flag indicating if the operation is canceled and check it inside your entryPoint function.

 class Program
    {
        static void Main(string[] args)
        {
            RunWithTimeout((token) =>
                               {
                                   Thread.Sleep(2000);
                                   if (token.Cancel)
                                   {
                                       Console.WriteLine("Canceled");
                                   }
                               }, 1000);

            Console.ReadLine();
        }

        private class Token
        {
            public bool Cancel { get; set; }
        }

        static void RunWithTimeout(Action<Token> entryPoint, int timeout)
        {

            Token token = new Token();

            var thread = new Thread(() => entryPoint(token)) { IsBackground = true };

            thread.Start();

            if (!thread.Join(timeout))
                token.Cancel = true;
        }
    }
Community
  • 1
  • 1
Andrei Schneider
  • 3,618
  • 1
  • 33
  • 41
  • I can't check the value of token.Cancel everywhere. If I need to stop after 1 second I have to stop after 1 second, no matter where I am. – Nick Nov 07 '12 at 23:10
  • Another solution is to use a child process... but it's more resource-consuming. – Andrei Schneider Nov 07 '12 at 23:19