Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cancel execution of command if it takes too long

I am currently working on a piece of software that uses an assembly from a different department.
I call a Method from this assembly like this:

using (var connection = (IConnection) Factory.GetObject(typeof (IConnection)))

The code used to work perfectly fine. But for the last few minutes it seemed like my program was doing nothing when I tried to launch it. Pressing pause while debugging showed me it got "stuck" at the line above.
My guess is they're just doing some maintenance or something but that's not the point here.
So I though it would be nice to tell the user what went wrong if the program doesn't start. Something simple like

MessageBox.Show("Could not connect", "Connection Error");

And then close the program. My question is:
How do I terminate the execution of a command after a set amount of time and jump somewhere else?
My guess would be moving it to a separate thread and then putting the calling thread to sleep for a few seconds after which it disposes of the extra thread if it isn't completed yet. But that seems really dirty to me and there's got to be a better way.

like image 830
Wilsu Avatar asked Dec 11 '22 18:12

Wilsu


2 Answers

Your question can be separated into two parts:

  1. How to terminate the execution of a command?

The only way is to abort the thread. But don't do it. There is no guaranteed and safe way. There are such methods like Thread.Interrupt and Thread.Abort that can wake up the thread. But they will work only if the thread is in the WaitSleepJoin state and it hangs in managed code.

Seems like you already know it. But once again, if something inside the assembly hangs infinitely the execution of code then the thread is probably "gone". So you are right that the program should be closed.

  1. ... jump somewhere else?

Good approach is using of TPL and async model. Here is an extension method to wrap up any Task and expires after timeout.

public static async Task TimeoutAfter(this Task task, int millisecondsTimeout)
{
    if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
        await task;
    else
        throw new TimeoutException();
}

Then use it

try
{
    using (var result = await Task.Run(() => (IConnection)Factory.GetObject(typeof(IConnection))).TimeoutAfter(1000))
    {
        ...
    }
}
catch (TimeoutException ex)
{
    //timeout
}

Here you can find more information

like image 154
Dmitry Ponomarenko Avatar answered Dec 30 '22 19:12

Dmitry Ponomarenko


A simple way of doing it without extra libraries or extension methods:

using ( var task = new Task<IConnection>( () => Factory.GetObject( typeof( IConnection ) ) ) )
{
    task.Start();

    if( !task.Wait( timeoutMilliseconds ) )
    {
        throw new TimeoutException();
    }

    IConnection result = task.Result;
}

Task.Wait does what you want, because you can throw an exception if it returns false (task didn't complete in time.)

It's even simpler if you have an Action that doesn't return something:

if ( !Task.Run( action ).Wait( timeoutMilliseconds ) )
{
    throw new TimeoutException();
}

Where action is some Action or lambda.

like image 20
Glazed Avatar answered Dec 30 '22 17:12

Glazed