【问题标题】:Is it possible for exceptions to be caught asynchronously without awaiting a task?是否可以在不等待任务的情况下异步捕获异常?
【发布时间】:2018-10-17 15:13:23
【问题描述】:

我有一个低级 CAN 设备类,我想为其创建一个 onMessageReceive 事件。我有几个可以使用这个 CAN 类的实例的高级设备类。我想将高级设备类的消息解析器附加到低级 CAN 设备 onMessageReceive 事件。这样,当低级类接收到一个数据包时,低级读取器任务会将其解析为高级类。放入代码中,如下所示。

void Main()
    {
        try
        {
            using (HighLevelDevice highLevelDevice = new HighLevelDevice())
            {
                while (true)
                {
                    // Use the properties/fields in highLevelDevice to make testing decisions.
                }
            }
        }
        catch (Exception)
        {
            // If the low level CAN reader task encounters an error I would like for it to asynchronously propogate up to here.
            throw;
        }
    }


    public class HighLevelDevice
    {
        private LowLevelCan lowLevelCanInstance;

        public HighLevelDevice()
        {
            lowLevelCanInstance = new LowLevelCan(this.ProcessPacket);
        }

        private void ProcessPacket(Packet packet)
        {
            // Convert packet contents into high level device properties/fields.
        }
    }

    public class LowLevelCan
    {
        private delegate void ProcessPacketDelegate(Packet packet);

        private ProcessPacketDelegate processPacket;

        private Task readerTask;

        public LowLevelCan(Action<Packet> processPacketFunction)
        {
            processPacket = new ProcessPacketDelegate(processPacketFunction);
            readerTask = Task.Run(() => readerMethod());
        }

        private async Task readerMethod()
        {
            while(notCancelled) // This would be a cancellation token, but I left that out for simplicity.
            {
                processPacket(await Task.Run(() => getNextPacket()));
            }
        }

        private Packet getNextPacket()
        {
            // Wait for next packet and then return it.
            return new Packet();
        }
    }

    public class Packet
    {
        // Data packet fields would go here.
    }

如果在 getNextPacket 中抛出异常,我希望它在 main.xml 中被捕获。这有可能吗?如果我偏离了基础并且完全误解了异步,我深表歉意。如果这样的事情是可能的,我怎么能改变我的方法来实现它?我可以定期检查阅读器的状态,但如果可能的话,我想避免这种情况。

这个实现会杀死阅读器,但 highLevelDevice 线程不经意地继续。如果我存储错误并偶尔在主线程上检查状态,那就没问题了。如果可能的话,我只想找到一个避免这种情况的解决方案。

我尝试了在 highLevelDevice 退出的线程上创建的错误报告事件和进度报告的变体。这些不能按预期工作/或我不明白他们在做什么。

【问题讨论】:

  • 你试过这个,发现它不起作用?这通常是检查某事是否可能的最简单方法......
  • 我已经尝试了几种变体。当抛出异常时,读取器线程将死亡,但创建高级设备的线程继续在不经意间运行。
  • 如果有问题,那将是很好的信息。随意edit您的问题,包括您尝试过的内容。请参阅How to Ask,了解有关我们在问题中寻找什么的更多提示。我们可以看到问题的编辑历史,因此无需告诉我们您编辑了什么。
  • 您的意图是低级设备将运行一个循环,通过事件将更改推送到高级设备(此时您的代码仅读取单个数据包)?而您的Main 循环访问高级设备而不关心其状态在后台发生变化?
  • @pere57 这就是我想做的。我将其编写为示例代码,以显示我想在实际代码中实现的目标。我修复了丢失的 while 循环。谢谢。真正的数据包具有时间戳信息,如果我想知道上次更改的时间,我可以检查这些信息。通常虽然我不关心通知所有更新的主循环。我确实关心主循环收到接收循环中的错误通知。错误,例如:无数据、错误数据或硬件断开连接。据我所知,我必须检查任务状态才能看到错误。我只是希望有人能告诉我另一种方式。

标签: c# asynchronous exception-handling async-await producer-consumer


【解决方案1】:

当您想异步启动一个方法并在稍后与它同步以获得结果时,您的标题问题适用。但是,您问题的主体所描述的实际上是对共享状态(高级设备)的并发访问。该状态是从您的 Main 线程读取的,但由您的低级设备写入后台线程。

解决方案是在高级设备中创建一个Error 属性,该属性可用于协调两个线程之间的错误处理:

  • 捕获低级设备引发的任何异常并将它们传播到高级设备(见下文),高级设备会将错误存储在属性Error 中。
  • 将 HL 设备的所有读取封装在属性中,以便在读取时可以检查Error。如果发生错误,则抛出带有详细信息的异常(在Main 中捕获和处理。)

最终结果是来自低级设备的异常已传播到Main

作为旁注,您的问题暗示了基于任务的异步模式,但您的低级设备实际上是以基于事件的方式编写的。见Asynchronous programming patterns

每种模式都有特定的传播错误的方法:

您的Task.Run 实际上只具有将低级设备循环放在与Main 不同的线程上的效果您不能等待readerTask,因为它代表整个处理循环而不是单个数据包更新。各个数据包更新是通过事件通知的。

总而言之,当低级设备捕获异常时,它应该引发一个事件并在事件的事件参数中传递异常的详细信息。高级设备将接收事件并将详细信息存储在其Error 属性中。这一切都发生在您的后台线程上。在主线程上,当在高级设备上读取属性期间检测到错误时,getter 应该抛出带有 Error 详细信息的异常,以在 Main 中处理。

【讨论】:

  • 我想我应该提到我考虑过这一点。但是,这意味着我必须将异常检查写入高级设备中的每个属性获取器。这是不幸的,因为每个设备都可以有数百个属性都需要这个检查。但是,解决方案就是解决方案。谢谢@pere57。
【解决方案2】:

没有。我已经对此进行了测试,您必须等待任务完成才能抛出异常。等待任务或使用 Task.Wait() 等待任务完成。

我尝试使用此代码,但没有捕获异常。

        try
        {
            var task = Task.Run(() => WaitASecond());

            task.ContinueWith(failedTask => throw failedTask.Exception, TaskContinuationOptions.OnlyOnFaulted);
        }
        catch (Exception ex)
        {
            throw;
        }

当我在var task = Task.Run(() =&gt; WaitASecond()); 下添加task.Wait() 时,它捕获了一个聚合异常并将其抛出。

您必须等待所有任务完成才能捕获异常并将其抛出到 Main()。

【讨论】:

    猜你喜欢
    • 2017-03-23
    • 2018-09-06
    • 1970-01-01
    • 2014-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-21
    • 1970-01-01
    相关资源
    最近更新 更多