我调用了一个非托管代码库,如果传递了错误的参数,它会挂起。
IWorkspaceFactory workspaceFactory = _workspaceFactory.OpenFromString(connectionString);
如果 connectionString 的值无效,则 OpenFromString 会挂起。如果我中断执行,我可以从调用堆栈中看到控件仍然是非托管库。不幸的是,这种方法没有提供超时。我需要做的是,在我的托管代码中,在一段特定的超时时间后取消呼叫。我尝试了几种方法,包括以下内容:
Task<IWorkspace> task = new Task<IWorkspace>(() =>
{
return _workspaceFactory.OpenFromString(connectionString);
});
if (!task.Wait(10000))
{
throw new TimeoutException();
}
IWorkspace workspace = task.Result;
在这种情况下, task.Wait()始终返回false,而 task.Result()始终为null
,即使有效值为传递 connectionString 。
我也尝试了以下方法:
IWorkspace workspace = null;
Thread thread = new Thread(() =>
{
workspace = _workspaceFactory.OpenFromString(connectionString);
});
thread.Start();
if (!thread.Join(10000))
{
throw new TimeoutException();
}
在这种情况下,如果传递 connectionString 的有效值,则执行按预期工作,但如果传递的值无效,则执行仍会在非托管代码中挂起,并且线程.Join()永远不会回来。
有关如何执行此操作的任何建议吗?
答案 0 :(得分:1)
在第一个代码片段中,您没有事件启动任务:
Task<IWorkspace> task = new Task<IWorkspace>(() =>
{
return _workspaceFactory.OpenFromString(connectionString);
});
if (!task.Wait(10000))
{
throw new TimeoutException();
}
您需要先使用task.Run()
启动它。检查"Task.Factory.StartNew" vs "new Task(...).Start"和Task.Run vs Task.Factory.StartNew。
无论如何,这不会解决原来的问题:
如果传递了connectionString的有效值,则执行为 预期,但如果传递的值无效,则执行仍会挂起 在非托管代码中
你在这里处理一些遗留的和明显错误的代码。将其修复到非托管端将是最佳选择,但可能您无法访问源。
您可以使用Stephen Toub所描述的技术"How do I cancel non-cancelable async operations?"但是,在您描述的场景中,您可能会非常快地耗尽线程池,而不会提及OpenFromString
可能获取的其他系统资源
我认为在这种情况下你能做的最好的事情就是将WorkspaceFactory
对象卸载到单独的帮助程序(而不是你自己进程中的一个线程)并通过远程处理调用它。如果呼叫超时,您终止并重新启动帮助程序。操作系统将在清理资源方面做得很好。
您可以将此流程打包为self-hosted WCF service,也可以将其作为out-of-proc COM singleton运行。
<强>更新强>:
我理解你关于让线程运行的观点,但我想我 可以使用这个
如果您乐意无限期地阻止池线程,请执行以下操作:
Task<IWorkspace> task = Task.Run(() =>
{
return _workspaceFactory.OpenFromString(connectionString);
});
if (!task.Wait(10000))
{
throw new TimeoutException();
}
IWorkspace workspace = task.Result;
我还有另外一个问题:您的_workspaceFactory
可能不是线程安全的,这通常是遗留代码的情况。也就是说,它可能只在最初创建的同一个线程上被调用。
答案 1 :(得分:1)
你应该找出库无限期挂起的原因,这听起来像是一个严重的错误。也许有一种方法可以验证 connectionString 并抛出异常?
除此之外,Stephen Toub在他的文章.NET Memory Allocation Profiling with Visual Studio 2012中写了一个可以帮助你达到你想要的东西的扩展方法。
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(() => tcs.TrySetResult(true)))
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
return await task;
}
该方法创建一个可以取消的代理任务,该任务包装原始任务。扩展方法使用TaskCompletionSource<bool>
创建另一个任务,并将其注册到CancellationToken
,然后使用Task.WhenAny
await
完成任一任务。
最终结果是通过CancellationToken
取消将取消代理任务,而原始任务将继续在后台运行并将消耗一个线程。如果非托管库永久挂起线程将永远消耗,这是非常糟糕的。但如果操作最终超时,这可能是一个可接受的解决方案。就客户而言,任务将被取消。
因此,您可以添加一个返回任务的方法:
public Task<IWorkspace> GetWorkscpaceAsync(string connectionString)
{
Task<IWorkspace> task = new Task<IWorkspace>.Run(() =>
{
return _workspaceFactory.OpenFromString(connectionString);
});
}
然后使用这样的扩展方法:
var tcs = new CancellationTokenSource(1000);
IWorkspace result = await GetWorskpaceAsync(connectionString).WithCancellation(tcs.Token);
答案 2 :(得分:0)
我有两个主题:
从头开始
new,调用_workspacefactory。
启动线程2后,让线程1等待x毫秒。如果_workspacefactory线程成功,则将共享变量更改为true。当线程1在等待之后继续时,让它检查公共变量。如果变量是假杀死线程2。