0

I have a low level CAN device class that I would like to create an onMessageReceive event for. I have several high level device classes that could use an instance of this CAN class. I would like to attach the high level device class' message parser to the low level CAN device onMessageReceive event. Such that when the low level class receives a packet it is parsed into the high level class by the low level reader task. Put into code it would look like the following.

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.
    }

If an exception is thrown in getNextPacket I would like that to be caught in main. Is this possible in any way? If I am way off base and completely misunderstanding async I apologize. If something like this is possible how could I change my approach to achieve it? I could check the state of the reader periodically, but I would like to avoid that if possible.

This implementation will kill the reader, but the highLevelDevice thread continues obliviously. This would be okay if I stored the error and checked the status occasionally on the main thread. I would just like to find a solution that avoid that, if possible.

I have tried variations of error reporting events and progress reporting created on the thread that the highLevelDevice exits on. These do not work as expected/or I do not understand what they are doing properly.

OsakaRhymes
  • 96
  • 1
  • 8
  • Have you tried this and found it doesn't work? That's usually the easiest way of checking if something is possible... – Heretic Monkey Oct 17 '18 at 15:17
  • I have tried several variations of this. The reader thread will die when an exception is thrown, but the thread the high level device is created on continues to run obliviously. – OsakaRhymes Oct 17 '18 at 15:22
  • 1
    That would be good information to have *in the question*. Feel free to [edit] your question to include what you've tried. See [ask] for more tips on what we look for in questions. We can see the edit history of the question, so there's no need to tell us what you've edited. – Heretic Monkey Oct 17 '18 at 15:24
  • Your intent is that the low-level device is going to run a loop where it pushes changes to the high-level device through events (at the moment your code only reads a single packet)? And your `Main` loop accesses the high-level device without caring that its state is changing in the background? – pere57 Oct 18 '18 at 20:16
  • @pere57 That is what I want to do. I wrote this as example code to show what I want to achieve in my actual code. I fixed the missing while loop. Thanks. The real packets have time stamp information that I can check if I wish to know when something changed last. Usually though I do not care for the main loop to be notified of all updates. I do care that the main loop be notified of errors within the receive loop. Errors such as: no data, bad data, or a hardware disconnect. As far as I know I would have to check the task state to see errors. I am just hoping someone can show me another way. – OsakaRhymes Oct 18 '18 at 20:55

2 Answers2

0

Your title question applies when you want to start a method asynchronously and at a later time synchronize with it to get the result. However, what the body of your question describes is really concurrent access to shared state (the high-level device.) The state is read from your Main thread but written to on a background thread by your low-level device.

The solution is to create an Error property in the high-level device which can be used to coordinate error-handling across the two threads:

  • catch any exceptions thrown by your low-level device and propagate them to the high-level device (see below), which will store the error in a property Error.
  • encapsulate all reads of the HL device in properties so that on read you can check Error. If an error has occurred, throw an exception with the details (to be caught and dealt with in Main.)

The net effect is that exceptions from the low-level device have been propagated to Main.

As a side note, your question implies the task-based asynchronous pattern but your low-level device is actually written in an event-based manner. See Asynchronous programming patterns.

Each pattern has a specific method for propagating errors:

Your Task.Run really only has the effect of putting the low-level device loop on a different thread to Main You can't await readerTask because it represents the processing loop as a whole and not an individual packet update. The individual packet updates are notified through events instead.

To summarise, when the low-level device catches an exception, it should raise an event and pass the details of the exception in the event's event args. The high-level device will receive the event and store the details in its Error property. This all happens on your background thread. On the main thread, when an error is detected during a property read on the high-level device, the getter should throw an exception with the Error details to be handled in Main.

pere57
  • 652
  • 9
  • 18
  • 1
    I guess I should have mentioned I considered this. However, it means that I have to write the exception check into every property getter in the high level device. This is unfortunate because each device can have hundreds of properties that will all need this check. However, a solution is a solution. Thanks @pere57. – OsakaRhymes Oct 18 '18 at 22:34
-1

No. I've tested this and you do have to wait for the task to complete in order to throw the exception. Either await the Task or use Task.Wait() to wait for the task to complete.

I tried using this code and it didn't catch the exception.

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

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

When I added task.Wait() under var task = Task.Run(() => WaitASecond()); it caught an aggregate exception and threw it.

You'll have to wait for all your tasks to complete to catch the exception and throw it up to Main().