views:

124

answers:

4

[VS 2010 Beta with .Net Framework 3.5]

I've written a C# component to asynchronously monitor a socket and raise events when data is received. I set the VB form to show message boxes when the event is raised. What I've noticed is that when the component raises the event synchronously, the message box blocks the component code and locks the form until the user closes the message. When it's raised asynchronously, it neither blocks the code, nor locks the form.

What I want is a way to raise an event in such a way that it does not block the code, but is called on the same thread as the form (so that it locks the form until the user selects an option.)

Can you help me out? Thanks.

[Component]

using System;
using System.Threading;
using System.ComponentModel;

namespace mySpace
{
    public delegate void SyncEventHandler(object sender, SyncEventArgs e);
    public delegate void AsyncEventHandler(object sender, AsyncEventArgs e);

    public class myClass
    {
        readonly object syncEventLock = new object();
        readonly object asyncEventLock = new object();

        SyncEventHandler syncEvent;
        AsyncEventHandler asyncEvent;

        private delegate void WorkerDelegate(string strParam, int intParam);

        public void DoWork(string strParam, int intParam)
        {
            OnSyncEvent(new SyncEventArgs());
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
            WorkerDelegate delWorker = new WorkerDelegate(ClientWorker);
            IAsyncResult result = delWorker.BeginInvoke(strParam, intParam, null, null);
        }

        private void ClientWorker(string strParam, int intParam)
        {
            Thread.Sleep(2000);
            OnAsyncEvent(new AsyncEventArgs());
            OnAsyncEvent(new AsyncEventArgs());
        }

        public event SyncEventHandler SyncEvent
        {
            add { lock (syncEventLock) syncEvent += value; }
            remove { lock (syncEventLock) syncEvent -= value; }
        }
        public event AsyncEventHandler AsyncEvent
        {
            add { lock (asyncEventLock) asyncEvent += value; }
            remove { lock (asyncEventLock) asyncEvent -= value; }
        }

        protected void OnSyncEvent(SyncEventArgs e)
        {
            SyncEventHandler handler;
            lock (syncEventLock) handler = syncEvent;
            if (handler != null) handler(this, e, null, null); // Blocks and locks
            //if (handler != null) handler.BeginInvoke(this, e, null, null); // Neither blocks nor locks
        }
        protected void OnAsyncEvent(AsyncEventArgs e)
        {
            AsyncEventHandler handler;
            lock (asyncEventLock) handler = asyncEvent;
            //if (handler != null) handler(this, e, null, null); // Blocks and locks
            if (handler != null) handler.BeginInvoke(this, e, null, null); // Neither blocks nor locks
        }
    }
}

[Form]

Imports mySpace

Public Class Form1

    Public WithEvents component As New mySpace.myClass()

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        component.DoWork("String", 1)
    End Sub

    Private Sub component_SyncEvent(ByVal sender As Object, ByVal e As pbxapi.SyncEventArgs) Handles component.SyncEvent
        MessageBox.Show("Synchronous event", "Raised:", MessageBoxButtons.OK)
    End Sub

    Private Sub component_AsyncEvent(ByVal sender As Object, ByVal e As pbxapi.AsyncEventArgs) Handles component.AsyncEvent
        MessageBox.Show("Asynchronous event", "Raised:", MessageBoxButtons.OK)
    End Sub
End Class
A: 

Yes, you can use the Invoke() method on the form (or any Control) to synchronize and execute any delegate in a synchronous way in the UI thread.

Lucero
I don't think that's what he meant. He wants an easy way to "lock" the form but he still wants his UI thread to pump messages. That's how I read his question, anyway.
dss539
`Invoke` will block the component code, I think the OP will need to use `BeginInvoke` to execute on the UI thread but not block the component code.
João Angelo
I've tried using Invoke() instead of BeginInvoke(), and it works on the form, but it blocks the component code. Also, calling Invoke() within a method called with BeginInvoke() doesn't change anything.(Where can I find formatting guidelines for posts and comments?)
Daniel Rasmussen
@cyclotis04 - you can find some formatting syntax here http://stackoverflow.com/editing-help
dss539
+5  A: 

You need to call the form's BeginInvoke method (only), which will run a delegate on the form's UI thread (thus blocking the form), without blocking the calling thread to wait for the call to finish.

If you don't have a reference to the form instance, you can save SynchronizationContext.Current from the UI thread, then call Post on the SynchronizationContext instance, which will be equivalent.

SLaks
Now that I understand his requirements, I believe you are correct. +1
dss539
Alright, I think I understand why this needs to happen, but I'm a little confused exactly how to get it to work. Where do I save the UI thread's context, and how do I pass it to my asynchronous thread?
Daniel Rasmussen
Save it into a field in your class from the UI thread (probably in `DoWork`)
SLaks
Thanks! I think I've got it working now.
Daniel Rasmussen
A: 

If I understand you correctly, I think you are approaching the problem in the wrong way.

I believe you need to explicitly disable the controls on the form whenever you display the message box and then re-enable them when the user closes the box.

Please clarify; do you want the UI thread to pump messages while the Box is displayed? (e.g. so the form repaints itself)

dss539
You are wrong. He needs to call `ShowDialog` on the UI thread.
SLaks
He wants it to pump messages, but not respond to user input.
SLaks
If I raise an event without using any BeginInvoke()s, then the messagebox locks the form until the user makes a selection. If any BeginInvoke()s are involved, the form doesn't lock.
Daniel Rasmussen
Exactly. See my answer.
SLaks
@cyclotis04 - I'm convinced that SLaks is correct. The key is that you call the BeginInvoke on the **VB form**.
dss539
+1  A: 

I recommend using SynchronizationContext.Post, passing the dialog box code in a delegate. This will not block the (other thread's) code but will execute the dialog box code in the UI thread, so it will be blocked.

Stephen Cleary