强制取消 API 可能挂起的任务
Force Cancel Task with API that might hang
我目前正在使用串行端口,我使用的 API 有时会在读取时挂起,即使设置了自己的超时也是如此。
这不是什么大问题,但发生这种情况时我需要做一些工作并且需要关闭挂起的线程。我已经尝试过以下方法,但它一直给我带来问题,因为 API 调用没有终止,但允许在其余代码继续时继续,并且 TimeoutException
被抛出。我如何使用 Task
s 在一定时间后取消挂起的任务?
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");
}
CancellationToken
是合作取消的形式。您需要在执行操作时审核令牌并观察是否已请求取消。
从您的代码块来看,您似乎有一个很长的 运行 同步操作,您将其卸载到线程池线程。如果是这种情况,请查看是否可以将该串行调用分离到块,您可以在读取块后轮询令牌。如果您不能,则无法取消。
请注意,要请求取消,您必须创建一个 CancellationTokenSource
,稍后您可以调用它的 Cancel()
方法。
附带说明一下,串行端口是异步 IO,您可以自然地使用异步 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();
}
}
如果您需要在这两个进程之间传递任何结果,您可以通过多种机制来实现。一种是编写和读取流程的标准输出。
遗憾的是我不能使用任何其他框架,而且我不能只更改我正在调用的 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;
}
}
我目前正在使用串行端口,我使用的 API 有时会在读取时挂起,即使设置了自己的超时也是如此。
这不是什么大问题,但发生这种情况时我需要做一些工作并且需要关闭挂起的线程。我已经尝试过以下方法,但它一直给我带来问题,因为 API 调用没有终止,但允许在其余代码继续时继续,并且 TimeoutException
被抛出。我如何使用 Task
s 在一定时间后取消挂起的任务?
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");
}
CancellationToken
是合作取消的形式。您需要在执行操作时审核令牌并观察是否已请求取消。
从您的代码块来看,您似乎有一个很长的 运行 同步操作,您将其卸载到线程池线程。如果是这种情况,请查看是否可以将该串行调用分离到块,您可以在读取块后轮询令牌。如果您不能,则无法取消。
请注意,要请求取消,您必须创建一个 CancellationTokenSource
,稍后您可以调用它的 Cancel()
方法。
附带说明一下,串行端口是异步 IO,您可以自然地使用异步 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();
}
}
如果您需要在这两个进程之间传递任何结果,您可以通过多种机制来实现。一种是编写和读取流程的标准输出。
遗憾的是我不能使用任何其他框架,而且我不能只更改我正在调用的 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;
}
}