使用可能挂起的API强制取消任务

3

我目前正在使用串口,并且我使用的 API 有时会在读取时挂起,即使设置了超时时间。

这不是一个大问题,但当发生这种情况并且挂起的线程需要关闭时,我需要做一些工作。我已经尝试使用以下方法,但它一直给我带来问题,因为 API 调用没有被终止,而是允许继续执行,而其余代码继续执行,然后抛出 TimeoutException。如何使用 Task 在一定时间后取消挂起的任务?

CancellationToken token = new CancellationToken();
var task = Task.Factory.StartNew(() => 
           {
               CallingAPIThatMightHang(); // Example
           }, token);

if (!task.Wait(this.TimeToTimeOut, token))
{
    throw new TimeoutException("The operation timed out");
}

简短回答:你不能;至少你不能可靠地这样做。相关阅读请参考http://blogs.msdn.com/b/ericlippert/archive/2010/02/22/should-i-specify-a-timeout.aspx - Sriram Sakthivel
使用Task真的是一种限制吗?只需创建线程,然后根据需要使用 Abort 终止即可。 - n0rd
我本来希望避免这种情况,但现在看来我不得不这么做。 - Androme
2个回答

3

CancellationToken 是一种协作取消的形式。在执行操作时,您需要控制令牌并检查是否已请求取消。

从您的代码块中看来,似乎您有一个长时间运行的同步操作,将其转移到线程池线程中。如果是这样,请尝试将串行调用分成块,在每个块读取后轮询令牌。如果您无法这样做,则无法取消操作。

请注意,要请求取消,您需要创建一个 CancellationTokenSource,然后稍后可以调用其 Cancel() 方法。

顺便提一下,串口是异步 I/O,您可以使用自然的异步 API,而不是将同步操作转移到线程池线程上。

编辑:

@HansPassant 给出了更好的建议。在另一个进程中运行第三方调用,您保留对该进程的引用。需要终止它时,杀死该进程。

例如:

void Main()
{
    SomeMethodThatDoesStuff();
}

void SomeMethodThatDoesStuff()
{
   // Do actual stuff
}

然后在单独的进程中启动它:

private Process processThatDoesStuff;

void Main()
{
    processThatDoesStuff = Process.Start(@"SomeLocation");
    // Do your checks here.

    if (someCondition == null)
    {
        processThatDoesStuff.Kill();
    }
}

如果需要在这两个进程之间传递任何结果,可以通过几种机制来实现。一种方法是通过写入和读取进程的标准输出来完成。

很遗憾,这是不可能的。 - Androme
似乎敌对的API并非由OP编写。 - Sriram Sakthivel
2
那么取消是不可能的 :/ - Yuval Itzchakov
2
我的在这里评论基本上说的是一样的 :) - Sriram Sakthivel
@downvoter,我很想知道你为什么要给我点踩。 - Yuval Itzchakov

-1

很遗憾,我不能使用任何其他框架,也无法更改我调用的API以便使用取消令牌。

这是我选择解决问题的方式。

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var result = TestThreadTimeOut();
            Console.WriteLine("Result: " + result);
        }
        catch (TimeoutException exp)
        {
            Console.WriteLine("Time out");
        }
        catch (Exception exp)
        {
            Console.WriteLine("Other error! " + exp.Message);
        }

        Console.WriteLine("Done!");
        Console.ReadLine();
    }

    public static string TestThreadTimeOut()
    {
        string result = null;
        Thread t = new Thread(() =>
        {
            while (true)
            {
                Console.WriteLine("Blah Blah Blah");
            }
        });
        t.Start();
        DateTime end = DateTime.Now + new TimeSpan(0, 0, 0, 0, 1500);
        while (DateTime.Now <= end)
        {
            if (result != null)
            {
                break;
            }
            Thread.Sleep(50);
        }
        if (result == null)
        {
            try
            {
                t.Abort();
            }
            catch (ThreadAbortException)
            {
                // Fine
            }
            throw new TimeoutException();
        }
        return result;
    }
}

看看我的编辑。也许这比使用“Thread.Abort”更好的想法。 - Yuval Itzchakov
1
Thread.Abort()是一把大锤,可能会导致程序处于损坏状态。我非常非常不建议使用它。 - Scott Chamberlain
你希望我使用什么替代方案? - Androme
@ScottChamberlain 我完全同意。同时,whileThread.Sleep 的使用让我感到不适... 使用 Timer 会更好,因为这样就不会有无谓的循环了... - user57508
2
@DoomStone Yuval Itzchakov的编辑答案是正确的方法。您需要创建第二个进程并在其中执行。如果需要在两个进程之间通信,请使用某种形式的IPC,我发现WCF over Named Pipes非常适合同一台机器的IPC,并且易于使用。 - Scott Chamberlain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接