如何使用try...catch语句来捕获ThreadPool.QueueUserWorkItem代码异常

3

我有很多异步命令。我想写一个 try..catch 语句而不需要重复太多的代码。例如:

_fooCommand = new AsynchronousCommand( () => { action } );
_barCommand = new AsynchronousCommand( () => { action } );

AsynchronousCommand是一个类,它使用ThreadPool.QueueUserWorkItem((state) => { action() });调用Action

当位于lambda内部时,try..catch可以很好地工作:

_fooCommand = new AsynchronousCommand( () => { try.exception.catch } );

当外界不成立时:

try
    _fooCommand = new AsynchronousCommand( () => {...} );
catch

未捕获异常。

编辑

我希望在使用command.DoExecute(this)执行命令时捕获异常,而不是在创建命令时捕获异常,并且如果可能的话,在lambda内部放置try..catch语句。


3
可能是重复的问题:如何从ThreadPool.QueueUserWorkItem中捕获异常? - HugoRune
不,我知道怎么做,我不知道怎么做而不重复,并且我不想将ThreadPool更改为其他内容。 - Tomasito
将 try/catch 移动到 AsynchronousCommand 构造函数中。 - 001
3
您知道实例化新的AsynchronousCommand并不会立即调用该命令吗?因此外部的try/catch语句不可能起作用。话虽如此,看起来您想要使用async/await的语义,它按照您期望的方式传播异常--但是它们不能完全替代QueueUserWorkItem,因为它可能或可能不利用线程。 - Kirk Woll
@KirkWoll _fooCommand.DoExecute(this) 调用。 - Tomasito
这是一个很好的观点 - 我的回答假设你的意思是在try catch中调用命令,而不仅仅是创建它。 - Chris Ballard
2个回答

3

异常会沿着抛出它们的线程的调用堆栈向上传播。由于命令在线程池线程上运行,因此它将在与您的try ... catch不同的线程上运行,因此不会被捕获。

编辑:假设您确实在try ... catch内部调用了该命令


为了解决我的问题,我必须将try..catch放在ThreadPool.QueueWorkItem内部,并最终从lambda外部调用Action(),如果我不想在AsynchronousCommand中编写异常处理代码(日志记录等)? - Tomasito
如果您正在线程池线程上运行此代码,则通常模式是在Action本身内部处理异常,而不是在用于创建AsynchronousCommand或用于调用它的代码中处理异常。 - Chris Ballard
Action是对外部服务的调用。我想捕获WebServiceExceptionWebException,但如果我需要捕获其他异常怎么办?命令可能会有所不同。 - Tomasito
只需定义自己的操作方法,该方法本身在“try ... catch”中包装外部调用,并在创建“AsynchronousCommand”时将其本身用作操作。 - Chris Ballard

2
您可以通过使用await来获得这些语义。当您await某个操作时,它将把方法的其余部分作为前一个方法的继续部分进行调度,这意味着该操作是异步执行的。然而,当等待的操作完成时,如果它代表会抛出异常的内容,那么该异常将被捕获,然后在您下一个继续的上下文中重新抛出,使您能够在单个try/catch中包装一系列异步操作,拥有您所需的语法和语义。一个简单的例子可能如下所示:
public static async Task Foo()
{
    try
    {
        await Task.Run(() => DoSomething());
        await Task.Run(() => DoSomethingElse());
    }
    catch(Exception e)
    {
        Console.WriteLine(e);
    }
}

DoSomethingDoSomethingElse将在线程池线程中运行,如果任何一个在运行时(而不是启动时)抛出异常,则会触发catch块。


不能保证DoSomethingDoSomethingElse会在单独的线程上执行。默认的任务调度器可能会发生这种情况,但使用Task.Run()时则不是显式保证。 - Odrade
@Odrade,任何情况下Run不会启动新线程的情况几乎肯定是您不希望它使用新线程的情况。也就是说,唯一这样的情况是当前线程也是线程池线程的时候,如果它不同步运行任务,它将最终被回收,因此最好不要回收并在内联中运行任务。任何需要Run不在当前线程中运行的情况都是它不会在当前线程中运行的情况。这不是完全相同的,但更优越。 - Servy
@Tomasito 那么你需要找到一种模拟Task模型的方法,并让AsynchronousCommand在调用操作时捕获异常,然后在请求结果时重新抛出它。如果您无法进行此类更改,或者没有机制可以让调用者访问结果(或者不在您想要使用try/catch的上下文中),则这是不可能的。 - Servy
@Servy - 我完全同意你的观点。我之所以对你答案中的最后一句话进行了一些小题大做,是因为我比较严谨。 - Odrade

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