1

Sorry for the title, i didn't find it easy to resume. My issue is that I need to implement a c# dll that implements a 'scan' method, but this scan, when invoked, must not block the main thread of the application using the dll. Moreover, it is a duty that after the scan resolves it rises an Event.

So my issue (in the deep) is that i'm not so experienced at c#, and after very hard investigation i've come up with some solutions but i'm not very sure if they are the "right" procedures.

In the dll i've come up with:

public class Reader
{
    public delegate void ReaderEventHandler(Object sender, AlertEventArgs e);
    public void Scan(String ReaderName)
    {
        AlertEventArgs alertEventArgs = new AlertEventArgs();
        alertEventArgs.uuiData = null;

        //Code with blocking scan function here

        if (ScanFinnished)
        {
            alertEventArgs.uuiData = "Scan Finnished!";                        
        }
        alertEventArgs.cardStateData = readerState[0].eventState;

        ReaderEvent(new object(), alertEventArgs);
    }

    public event ReaderEventHandler ReaderEvent;
}

public class AlertEventArgs : EventArgs
{
    #region AlertEventArgs Properties
    private string _uui = null;
    private uint cardState = 0;
    #endregion

    #region Get/Set Properties
    public string uuiData
    {
        get { return _uui; }
        set { _uui = value; }
    }
    public uint cardStateData
    {
        get { return cardState; }
        set { cardState = value; }
    }
    #endregion
}

While in the main app I do:

Reader reader;
Task polling;
String SelectedReader = "Some_Reader";

private void bButton_Click(object sender, EventArgs e)
{
    reader = new Reader();
    reader.ReaderEvent += new Reader.ReaderEventHandler(reader_EventChanged);
    polling = Task.Factory.StartNew(() => reader.Scan(SelectedReader));
}


void reader_EventChanged(object sender, AlertEventArgs e)
{
    MessageBox.Show(e.uuiData + "  Estado: " + e.cardStateData.ToString("X"));
    reader.Dispose();
}

So here, it works fine but i don't know if it's the proper way, in addition i'm not able to handle possible Exceptions generated in the dll.

Also tried to use async/await but found it difficult and as I understand it's just a simpler workaround Tasks.

What are the inconvinients of this solution? how can i capture Exceptions (are they in other threads and that's why i cant try/catch them)? Possible concept faults?

Joster
  • 359
  • 1
  • 4
  • 19

1 Answers1

1

When your class sends events, the sender usually is that class, this. Having new object() as sender makes absolutely no sense. Even null would be better but... just use this.

You shouldn't directly raise events as it might result in race conditions. Might not happen easily in your case but it's just a good guideline to follow. So instead of calling ReaderEvent(new object(), alertEventArgs); call RaiseReaderEvent(alertEventArgs); and create method for it.

For example:

private void RaiseReaderEvent(AlertEventArgs args)
{
  var myEvent = ReaderEvent; // This prevents race conditions
  if (myEvent != null) // remember to check that someone actually subscribes your event
    myEvent(this, args); // Sender should be *this*, not some "new object()".
}

Though I personally like a bit more generic approach:

private void Raise<T>(EventHandler<T> oEvent, T args) where T : EventArgs
{
   var eventInstance = oEvent;
   if (eventInstance != null)
      eventInstance(this, args);
}

Which can then be used to raise all events in same class like this:

Raise(ReaderEvent, alertEventArgs);

Since your scan should be non-blocking, you could use tasks, async/await or threads for example. You have chosen Tasks which is perfectly fine.

In every case you must understand that when you are not blocking your application, your application's main thread continues going like a train. Once you jump out of that train, you can't return. You probably should declare a new event "ErrorEvent" that is raised if your scan-procedure catches an exception. Your main application can then subscribe to that event as well, but you still must realize that those events are not (necessarily) coming from the main thread. When not, you won't be able to interact with your GUI directly (I'm assuming you have one due to button click handler). If you are using WinForms, you'll have to invoke all GUI changes when required.

So your UI-thread safe event handler should be something like this:

void reader_EventChanged(object sender, AlertEventArgs e)
{
 if (InvokeRequired) // This true for others than UI Thread.
 {
   Invoke((MethodInvoker)delegate 
   {
      Text = "My new title!";
   });
 }
 else
    Text = "My new title!";
}

In WPF there's Dispather that handles similar invoking.

Simo Erkinheimo
  • 1,347
  • 9
  • 17
  • Thanks a lot @Simo, that was clarifying... I may still have some doubts about what you pointed out with the train's metaphore: don't you return to the main thread when the event finnishes? is it the event or the task what finnishes, or are both the same, or the event is within the task? that's part of the trouble I have with mixing tasks and events... In the first place i didn't use dispose(), and everytime i hit the button a new task stacked, performing twice... and i don't understand why the task doesn't finnish if i don't reader.dispose(). – Joster Jan 09 '18 at 16:39
  • 1
    Eg.Button click is handled in your dialogs main thread and If you block this with your procedure, you'll see it as GUI freeze. Since you don't block it, the main thread keeps running the message pump and the GUI stays responsive. "Non-blocking" basically means running a separate thread. That thread just begins, runs and dies, but never magically jumps back to main thread (the train of executing the main thread is far away already). Events have nothing to do with this, they are just invoked in callee's thread. So callee is "worker thread", dialog changes must be done via Invoke. – Simo Erkinheimo Jan 09 '18 at 17:01
  • I'm sorry, i may look a little stubborn... I understand i need to invoke to access GUI controls while i'm in the event as the event is raised by the task i fired for the scanning, so i'm outside the main thread; but then the Event finnishes... shouldn't finnish the Task as well? if so, where does it leaves me? aren't I back in the main thread (as I can manipulate the GUI again)? – Joster Jan 09 '18 at 17:15
  • 1
    You aren't "back" in the main thread since you'll never leave it as the GUI stays responsive. It's your task that is spawned to somewhere else to do its job. Your task is finished when the function is was supposed to run is done and you are not notified about it in any way. The task might reserve some resources and whether or not you should dispose those... someone else knows better than me: https://stackoverflow.com/questions/5985973/do-i-need-to-dispose-of-a-task – Simo Erkinheimo Jan 09 '18 at 17:26