views:

613

answers:

3

I have, what probably is, a simple problem. I'm using interop to call an asynchronous function in CompactFramework. After I get result of the execution, I would like to raise an event, that will be caught by the form, and based on the results, I would render some data on the screen. The problem, however, is that when interop function returns a result, it returns it on a worker thread, and if I raise an event, it will stay on the worker thread and I can't render any data in the form, unless I use Invoke.

Can somebody suggest a way to merge worker thread onto the main thread? And raise an event from the main thread? I found a few examples that loop through the delegates subscribed to the event and use BeginInvoke to raise an event in main thread, however, they all use ISynchronizeInvoke, which is not available in Compact Framework.

My code is below:

public delegate void CellTowerFoundEventHandler(CellTower towerInfo);

public class CellTowerProvider
{
    public delegate void RILRESULTCALLBACK(uint dwCode, IntPtr hrCmdID, IntPtr lpData, uint cbData, uint dwParam);
    public delegate void RILNOTIFYCALLBACK(uint dwCode, IntPtr lpData, uint cbData, uint dwParam);
    private RILCELLTOWERINFO towerDetails;
    private CellTower cellTowerInfo;
    private IntPtr radioInterfaceLayerHandle;

    public CellTowerProvider()
    {
        radioInterfaceLayerHandle = IntPtr.Zero;
        IntPtr radioResponseHandle = IntPtr.Zero;

        // Initialize the radio layer with a result callback parameter.
        radioResponseHandle = RIL_Initialize(1, new RILRESULTCALLBACK(CellDataCallback), null, 0, 0, out radioInterfaceLayerHandle);

        // The initialize API call will always return 0 if initialization is successful.
        if (radioResponseHandle != IntPtr.Zero)
        {
            return;
        }

        // Query for the current tower data.
        radioResponseHandle = RIL_GetCellTowerInfo(radioInterfaceLayerHandle);
    }

    public void CellDataCallback(uint dwCode, IntPtr hrCmdID, IntPtr lpData, uint cbData, uint dwParam)
    {
        // Refresh the current tower details
        towerDetails = new RILCELLTOWERINFO();

        // Copy result returned from RIL into structure
        Marshal.PtrToStructure(lpData, towerDetails);

        cellTowerInfo = new CellTower()
        {
            TowerId = Convert.ToInt32(towerDetails.dwCellID),
            LocationAreaCode = Convert.ToInt32(towerDetails.dwLocationAreaCode),
            MobileCountryCode = Convert.ToInt32(towerDetails.dwMobileCountryCode),
            MobileNetworkCode = Convert.ToInt32(towerDetails.dwMobileNetworkCode),
        };

        if (CellTowerFound != null)
            CellTowerFound(cellTowerInfo);

        // Release the RIL handle
        RIL_Deinitialize(radioInterfaceLayerHandle);
    }

}
+2  A: 

Well, Invoke is the (proper) way to 'merge' (sync) a Thread with with the main thread. The easiest ways is to use a Control (eg MainForm) and call ctl.Invoke(myDelegate).

And second, we raise events and throw exceptions. Using the right words avoids confusion.

Henk Holterman
Right, I understand that Invoke would be the easiest way of doing this, however, I would like my code to be more or less reusable, and it would be a pain to use Invoke every time you get a callback in the UI. I would rather change my library so that it would raise events in the main thread all on its own.
Ilya Volodin
As far as i know, SynchronizationContext is not supported on CF. So you will have to improvise. If your lib has an Init function, require a Form to be specified. Something like that.
Henk Holterman
+2  A: 

This depends on what toolkit you're using to display data in your "main thread". If you're using Windows Forms, you should be able to use Control.Invoke or Control.BeginInvoke to marshal the event back onto the UI thread. This is supported in the Compact Framework.

Most samples use ISynchronizeInvoke instead of a control - but the code should be identical. You'll need a reference to a UI element in your code, though, to use with the call to BeginInvoke when you raise your event.

Reed Copsey
I've tried this instead of just firing the event: if (CellTowerFound != null) { foreach (Delegate d in CellTowerFound.GetInvocationList()) { Form f = d.Target as Form; if (f != null) { f.BeginInvoke(d); } } }However, that throws InvalidArguments exception in the Application. I would rather fix this in the library, and not in UI.
Ilya Volodin
If d.Target is actually a form, you should be able to use begin invoke - but "d" isn't enough info: You'll want: f.BeginInvoke( () => { d(this, EventArgs.Empty); }); - or something similar - d's the wrong type of delegate for begininvoke..
Reed Copsey
+2  A: 

Why not create a Control in the ctor of your library object? If you assume that the library instance is created in the UI thread, you can then call Control.Invoke on that control internal to your library, and the event call will be on the UI thread in the consumer.

Of course this isn't infallible. The consumer might create the instance in a worker, but it at least provides some level of what you're after and if they use a worker thread, they'd actually expect events to need an Invoke call anyway.

ctacke
It's a bit of a hack, but yeah, I guess this will have to do. Created a new label in the constructor, created a method and a delegate, when interop returns a value, I call Invoke on the label (label.Invoke(new CellTowerFoundEventHandler(this.getLocationInfo), new object[]{cellTowerInfo});) and from there fire my event. Event is now on the main thread. The only concern that I have, is that debugger still shows worker thread running. I hope GC will take care of it, but I'm not 100% sure about it.
Ilya Volodin
Why a Label? Just use a base Control. Also, always check InvokeRequired, and I'd recommend using BeginInvoke instead of Invoke so as not to allow a consumer to block your library.
ctacke