3

Is there a way in c# to call a method so that if the method takes to long to complete, the method will be canceled and it will return to the calling method? I think I can do this with threading but what if threading is not needed?

For reference, the method I may need to kill/stop/abort is calling the CorelDraw 15 API. This opens an instance of CorelDraw and I have received non-repeatable errors in this method. Meaning, I can process the same image twice and one time it will freeze or error and the other it will not.

The current solution to the issue I am using is to have a second application that does Process.Start(firstAppExecutablePath) and then checks a variable in a text file and if the variable doesn't change after 10 minutes, .Kill(); is called on the instance of the process. I would prefer to avoid this solution if possible as it seems clunky and prone to issues. Since it runs .Kill(); it is being very messy in how things close but generally does not cause an issue.

Ben Hoffman
  • 8,149
  • 8
  • 44
  • 71
  • 4
    If it's not threaded, then there is no way to check a timeout because the executing thread is executing your method. There is nothing to check "has it taken too long?" – dmeglio Jun 01 '16 at 18:50
  • 4
    Is your api async? This is what a CancellationToken is for. – Crowcoder Jun 01 '16 at 18:50
  • 1
    What is the method doing? – Luaan Jun 01 '16 at 18:53
  • The method I am calling opens a copy of CorelDraw and does some work. So I won't be able to use multiple threads. The issue I have with using await/async is that there is nothing for me to call within my method that uses async so then Visual Studio gives me a message stating that my method will be called synchronously. – Ben Hoffman Jun 01 '16 at 19:01
  • 1
    @BenHoffman I am unclear why that means you can't use threads. Just as you described a second program running to kill it in a comment to one of the answers, you could have another thread running to kill it. Not much difference. – dmeglio Jun 01 '16 at 19:33
  • @dman2306 I could use threads, but when I try to use await/async, the method I am attempting to call asynchronously has a green underline with this message: `warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.` I assume this is because within my method I have no additional async calls but I don't have anything to call asynchronously from within my method, so what do I do? – Ben Hoffman Jun 01 '16 at 19:44
  • Then you can't use `async`. The `async` keyword does nothing except allow you to use `await` inside the method. Putting `async` on the method signature does absolutely nothing to make it asynchronous. If you're code isn't asynchronouse (it doesn't sound like it is), then you can't use async/await to solve this problem. You must use threads. – dmeglio Jun 01 '16 at 19:46
  • @dman2306 So ManfredRadlwimmer's answer below is what I should be looking at? – Ben Hoffman Jun 01 '16 at 19:51
  • 1
    Since it's likely an unmanaged API, I do not believe his answer will work for you. – dmeglio Jun 01 '16 at 20:12

2 Answers2

1

Not built-in, no, since interrupting arbitrary code cannot be done safely (what if it's in the middle of calling a C library function (that doesn't support exceptions) which has just taken a global lock and needs to release it?).

But you can write such support yourself. I wouldn't add threads to the mix unless absolutely necessary, since they come with an entire new dimension of potential problems.

Example:

void Caller()
{
    int result;
    if (TryDoSomething(out result, 100)) {
        System.Console.WriteLine("Result: {0}", result);
    }
}

bool TryDoSomething(out int result, int timeoutMillis)
{
    var sw = Stopwatch.StartNew();
    result = 0x12345678;
    for (int i = 0; i != 100000000; ++i) {
        if (sw.ElapsedMilliseconds > timeoutMillis)
            return false;
        result += i / (result % 43) + (i % 19);
    }
    return true;
}
Cameron
  • 96,106
  • 25
  • 196
  • 225
  • This doesn't seem to really answer the question. The OP wants a single method call to time out whereas what you are doing is repeating a single brief operation for a certain period of time. Not the same thing in my view (though OP may beg to differ). – Chris Jun 01 '16 at 19:19
  • @Chris agreed. You're assuming the method can be modified to check how long it has been running. What if I'm trying to timeout an IO operation that just blocks? – dmeglio Jun 01 '16 at 19:20
  • `TryDoSomething` *is* a single method ;-) Most methods can be instrumented with checks for a timeout at strategic positions. The loop was just an example. And yes, this assumes control over the target method, which has since been revealed not to be the case for the OP. I'll leave the answer for the general case. I/O and other OS-level operations tend to have non-blocking versions that can be integrated with timeouts with enough effort. – Cameron Jun 01 '16 at 19:24
  • @Cameron I actually cannot check for timeouts at strategic points. The method I am calling uses the CorelDraw API which opens CorelDraw and modifies graphics. The issue ends up being that CorelDraw's API is buggy and doesn't always do the same thing. I have processed hundreds of graphics in a day and every few hundred something weird happens and CorelDraw freezes or gets stuck on a copy function. Right now a second application kills the first one when it runs too long and when it reruns the same image that caused the problem, no problem arises. – Ben Hoffman Jun 01 '16 at 19:31
  • @Ben: Hmm, that's annoying. I remember using CorelDraw ages ago, and it did crash occasionally then too. My answer obviously doesn't apply to your case, please ignore it! – Cameron Jun 01 '16 at 19:41
0

Threading is absolutely needed unless you are ok with checking the timeout from within the function - which probably you arn't. So here is a minimalistic approach with threads:

private static bool ExecuteWithTimeout(TimeSpan timeout, Action action)
{
    Thread x = new Thread(() => { action(); });
    x.Start();

    if (!x.Join(timeout))
    {
        x.Abort(); //Or Interrupt instead, if you use e.g. Thread.Sleep in your method
        return false;
    }

    return true;
}
Manfred Radlwimmer
  • 13,257
  • 13
  • 53
  • 62
  • 3
    It is not recommended to abort other threads. If the other thread is currently executing a static constructor, you risk never being able to construct an object of the type with that constructor in that process. See the note in [the documentation](https://msdn.microsoft.com/en-us/library/ty8d3wta(v=vs.110).aspx#Anchor_2). – Lasse V. Karlsen Jun 01 '16 at 18:59
  • @Cameron `x.Abort()` doesn't "cancel" it and the thread "happily continues to do its thing"? I find that hard to believe ... – Manfred Radlwimmer Jun 01 '16 at 19:02
  • @LasseV.Karlsen Yup, that would be bad. Which is why (if we knew what kin of code OP is trying to cancel) anything with a CancellationToken or at least and Interrupt instead of Abort would be preferable. – Manfred Radlwimmer Jun 01 '16 at 19:06
  • Ah, sorry, I didn't see the `Abort`. – Cameron Jun 01 '16 at 19:12
  • @ManfredRadlwimmer I am calling the CorelDraw API which sometimes gets stuck at odd points and either freezes or actually what I have seen more often is, when you copy shapes from one page to another it actually uses the clipboard and if the clipboard is empty, it throws up a window about pasting. What makes it extra interesting is that I check if the clipboard is empty right before pasting but that does not always seem to work! – Ben Hoffman Jun 01 '16 at 19:35
  • What if the clipboard contains a format that CorelDraw doesn't support? – Lasse V. Karlsen Jun 01 '16 at 19:52
  • @BenHoffman I see. Since you have no way of knowing what that API does internally (without painstakingly disassembling it) you might actually need to use `Abort`. I hope this works for you. – Manfred Radlwimmer Jun 01 '16 at 19:55
  • @LasseV.Karlsen I think that is where the problem arises. When the cut/copy fails to push anything to the clipboard, it throws a popup window on CorelDraw but gives the API no way to handle it. This causes the paste operation to never complete. Which is the whole need for threading in this case. – Ben Hoffman Jun 01 '16 at 20:09
  • 2
    The problem is `Abort` will not kill the thread while the thread is executing unmanaged code (which this API is almost certainly). Therefore, it will probably not work. `Abort` will wait until the unmanaged code finishes and the thread is executing managed code. – dmeglio Jun 01 '16 at 20:12