Events (part 3, advanced)

Events: Let’s go multithreading! We want the crème de la crème. Well, multitasking is also fine.

The code does not need to run in a specific sequence. The required independence is given. Again, there are many ways to use multithreading. An easy approach is to start a task in each method that is called by the event.

C# does support BeginInvoke() for delegates. This method is not supported by the .NET Compact Framework though. We don’t care, because our hardcore programs are for serious applications, definitely not for mobile phone apps. Let’s see how good BeginInvoke() works. Maybe we don’t have to reinvent the wheel.

BeginInvoke() initiates asynchronous calls, it returns immediately and provides the IAsyncResult, which can be used to monitor the progress of the asynchronous call.
EndInvoke() retrieves the results. It blocks until the thread has completed.

You have the following options after calling BeginInvoke():
1) Call EndInvoke() to block the current thread until the call completes.
2) Obtain the WaitHandle from IAsyncResult.AsyncWaitHandle, use its WaitOne() and then call EndInvoke().
3) Poll the IAsyncResult to check the current state, after it has completed call EndInvoke().
4) Pass a callback to BeginInvoke(). The callback will use the ThreadPool to notify you. In the callback you have to call EndInvoke().

public event EventHandler OnChange;

public void DoSomeWork(object sender, object e) {
    Thread.Sleep(2100);
    Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + " mission accomplished!");
} //

public void RunMyExample() {
    OnChange += new EventHandler(DoSomeWork);
    //OnChange += new EventHandler(DoSomeWork);
    //OnChange += new EventHandler(DoSomeWork);

    IAsyncResult lAsyncResult;

    Console.WriteLine("Choice 1");
    lAsyncResult = OnChange.BeginInvoke(this, EventArgs.Empty, null, null);
    OnChange.EndInvoke(lAsyncResult);

    Console.WriteLine("Choice 2");
    lAsyncResult = OnChange.BeginInvoke(this, EventArgs.Empty, null, null);
    lAsyncResult.AsyncWaitHandle.WaitOne();

    Console.WriteLine("Choice 3");
    lAsyncResult = OnChange.BeginInvoke(this, EventArgs.Empty, null, null);
    while (!lAsyncResult.IsCompleted) {
        Thread.Sleep(500);
        Console.WriteLine("work still not completed :(");
    }

    Console.WriteLine("Choice 4"); 
    OnChange.BeginInvoke(this, EventArgs.Empty, (xAsyncResult) => {
            Console.WriteLine("callback running");
            OnChange.EndInvoke(xAsyncResult);
        }, null);

    Console.WriteLine("press return to exit the program");
    Console.ReadLine();
}//    

example output:
Choice 1
Thread 6 mission accomplished!
Choice 2
Thread 6 mission accomplished!
work still not completed 😦
work still not completed 😦
work still not completed 😦
work still not completed 😦
Thread 10 mission accomplished!
work still not completed 😦
press return to exit the program
Thread 6 mission accomplished!
callback running

After having lived such a nice life, we have to face a major issue with BeginInvoke(). Uncomment “//OnChange += new EventHandler(DoSomeWork);”, don’t get annoyed now!
You will get an error message saying

“The delegate must have only one target”.

Although the delegate class can deal with multiple targets, asynchronous calls accept just one target.

So let’s try something else.

    public event EventHandler OnChange;

    public void DoSomeWork(object sender, object e) {
        Thread.Sleep(2100);
        Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + " mission accomplished!");
    } //

    public void RunMyExample() {
        OnChange += new EventHandler(DoSomeWork);
        OnChange += new EventHandler((sender, e) => { throw new Exception("something went wrong"); });
        OnChange += new EventHandler(DoSomeWork);

        EventHandler lOnChange = OnChange;
        if (lOnChange == null) return;   // just to demonstrate the proper way to call events, not needed in this example                
        foreach (EventHandler d in lOnChange.GetInvocationList()) {
            Task lTask = Task.Factory.StartNew(() => d(this, EventArgs.Empty));
            lTask.ContinueWith((i) => { Console.WriteLine("Task canceled"); }, TaskContinuationOptions.OnlyOnCanceled);
            lTask.ContinueWith((i) => { Console.WriteLine("Task faulted"); }, TaskContinuationOptions.OnlyOnFaulted);
            lTask.ContinueWith((i) => { Console.WriteLine("Task completion"); }, TaskContinuationOptions.OnlyOnRanToCompletion);
        }

        Console.WriteLine("press return to exit the program");
        Console.ReadLine();
    }//    

It seems that Microsoft has a serious bug here.This code does not execute properly each time. I guess it has to do with the asynchronous behaviour of StartNew(). It sometimes calls the wrong method DoSomeWork() three times and does not raise the exception.
It seems foreach overrides variable “d” before it is inserted in StartNew(). With a little tweak we can avoid this bug. We simply assign “d” to a new local variable. That way we have unique copies of “d”. Weird stuff, but that is the life of a coder sometimes.

public event EventHandler OnChange;

public void DoSomeWork(object sender, object e) {
    Thread.Sleep(2100);
    Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + " mission accomplished!");
} //

public void RunMyExample() {
    OnChange += new EventHandler(DoSomeWork);
    OnChange += new EventHandler((sender, e) => { throw new Exception("something went wrong"); });
    OnChange += new EventHandler(DoSomeWork);

    EventHandler lOnChange = OnChange;
    if (lOnChange == null) return;   // just to demonstrate the proper way to call events, not needed in this example                
    foreach (EventHandler d in lOnChange.GetInvocationList()) {
        EventHandler e = d;
        Task lTask = Task.Factory.StartNew(() => e(this, EventArgs.Empty));
        lTask.ContinueWith((i) => { Console.WriteLine("Task canceled"); }, TaskContinuationOptions.OnlyOnCanceled);
        lTask.ContinueWith((i) => { Console.WriteLine("Task faulted"); }, TaskContinuationOptions.OnlyOnFaulted);
        lTask.ContinueWith((i) => { Console.WriteLine("Task completion"); }, TaskContinuationOptions.OnlyOnRanToCompletion);
    }

    Console.WriteLine("press return to exit the program");
    Console.ReadLine();
}//    

Example output:
press return to exit the program
Thread 11 mission accomplished!
Thread 10 mission accomplished!
Task completion
Task faulted
Task completion

This tiny tweak worked. You just have to know the compiler bugs. I hope this little notice saves you at least 3 hours of work.
You probably remember that we faced a similar issue in my post “Exiting Tasks (advanced)” https://csharphardcoreprogramming.wordpress.com/2013/12/11/exiting-tasks/ . I would suggest to only use Task.Factory.StartNew() with caution. Maybe it only happens in conjunction with lambda expressions.
The following code is also running very well. The variable “d” is not used directly in Task.Factory.StartNew().

...
 foreach (EventHandler d in lOnChange.GetInvocationList()) {
            Action a = () => d(this, EventArgs.Empty);
            Task lTask = Task.Factory.StartNew(a);
            lTask.ContinueWith((i) => { Console.WriteLine("Task canceled"); }, TaskContinuationOptions.OnlyOnCanceled);
            lTask.ContinueWith((i) => { Console.WriteLine("Task faulted"); }, TaskContinuationOptions.OnlyOnFaulted);
            lTask.ContinueWith((i) => { Console.WriteLine("Task completion"); }, TaskContinuationOptions.OnlyOnRanToCompletion);
        }
...
Advertisements

About Bastian M.K. Ohta

Happiness only real when shared.

Posted on December 20, 2013, in Advanced, C#, Delegates, Professional, Threading and tagged , , , , , , , , , , , , , , , , . Bookmark the permalink. 1 Comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: