locked
C# -> JavaScript events (with threads) in WinRT RRS feed

  • Question

  • I'm trying to generate events from a C# class that will be handled by JavaScript functions. If I trigger the event from JS, it travels from JS -> C# -> JS just fine. If it's fired from C# (from a worker thread) it will invariably fail. I've tried using BeginInvoke instead of just calling the event object to have it run on the same thread, but that just fails differently. Anyone have an idea what I'm doing wrong? (code is below).

    C#:

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Windows.Foundation;
    
    namespace LibEventTest
    {
        sealed public class MyTimer
        {
            public event EventHandler<string> MyTimerEvent;
    
            public IAsyncAction FireTimerInBackground(string message)
            {
                return Task.Run(() =>
                {
                    FireTimerEvent(message);
                }).AsAsyncAction();
            }
    
            public void FireTimerEvent(string message)
            {
                int failureMode = 1;
                switch( failureMode )
                    case 1:
                        MyTimerEvent(this, message);
                        break;
                /* Throws:
    An exception of type 'System.InvalidCastException' occurred in LibEventTest.winmd but was not handled in user code
    Additional information: Unable to cast COM object of type 'System.EventHandler`1[System.String]' to class type 'System.EventHandler`1[System.String]'. Instances of types that represent COM components cannot be cast to types that do not represent COM components; however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface.
                 */
                    case 2:
                        MyTimerEvent.BeginInvoke(this, message, null, null);
                        break;
                /* Throws:
    Managed Debugging Assistant 'FatalExecutionEngineError' has detected a problem in 'C:\Windows\System32\WWAHost.exe'.
    Additional Information: The runtime has encountered a fatal error. The address of the error was at 0xf7634c65, on thread 0x1c6c. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.
                 * */
                    case 3:
                 // Throws the same as case 2
                        foreach (EventHandler<string> eh in MyTimerEvent.GetInvocationList())
                        {
                            eh.BeginInvoke(this, message, null, null);
                        }
                        break;
                }
            }
        }
    }
    

    JavaScript:

    (function () {
        "use strict";
    
        var app = WinJS.Application;
    
        app.onactivated = function (eventObject) {
            if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
                if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {
                    var myTimer = new LibEventTest.MyTimer()
                    myTimer.addEventListener("mytimerevent", function (ctr) {
                        document.getElementById("content").textContent = "Message: " + ctr;
                    });
                    myTimer.fireTimerEvent("From JavaScript");
                    setTimeout(function () {
                        myTimer.fireTimerEvent("From JavaScript, on timer.");
                        setTimeout(function () {
                            myTimer.fireTimerInBackground("From C#");
                        }, 2000);
                    }, 2000);
                }
                WinJS.UI.processAll();
            }
        };
    
        app.start();
    })();

    Monday, March 12, 2012 9:58 PM

Answers

All replies

  • Hi Sean,

    You can get back to the dispatcher thread by calling CoreDispatcher.InvokeAsync (the same as you would in a pure C# app).

    CoreDispatcher is not Xaml specific, but is in Windows.UI.Core and is available to all UI stacks.

    This is demonstrated in Walkthrough: Creating a basic Windows Runtime component in C++ and calling it from JavaScript. The syntax will be different for C#, but the basic idea is the same.

    Calling BeginInvoke on a delegate will run the delegate in a worker thread rather than marshaling it back to the UI thread.

    --Rob

    Monday, March 12, 2012 11:40 PM
    Moderator
  • Thank you! I've been beating my head against this problem for almost two days now. Using CoreDispatcher solved my problem. The only gotcha I'll add for anyone else reading is that CoreWindow.Current throws an error that it hasn't been implemented yet, so you'll have to call CoreWindow.GetForCurrentThread() on the UI thread and save it's dispatcher rather than just retrieving it as needed.
    • Proposed as answer by Givit Tuesday, June 12, 2012 3:20 PM
    • Unproposed as answer by Givit Tuesday, June 12, 2012 3:20 PM
    Tuesday, March 13, 2012 12:15 AM
  • I am having a similar problem with some of my WinRT events. I am generating the events in C# and trying to implement the handlers in JavaScript. It works fine when the handlers are in the main UI in JavaScript; however, if the handlers (and the calls that trigger them) are in a background task (created with Windows.ApplicationModel.Background.BackgroundTaskBuilder) in JavaScript then I get this exception:

    Unable to cast COM object of type 'System.EventHandler`1[MyNamespace.SendSurveyEventArgs]' to class type 'System.EventHandler`1[MyNamespace.SendSurveyEventArgs]'. Instances of types that represent COM components cannot be cast to types that do not represent COM components; however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface.

    SendSurveyEventArgs is declared as follows:

    public sealed class SendSurveyEventArgs { public int Status { get; set; } };

     

    Tuesday, June 12, 2012 3:19 PM
  • Hi Bob

    I am confused about using the Dispatcher to solve the problem.

    If a dispatcher has to be passed in, than how does some WRT component, for example Windows.Devices.Sensors.Accelerometer deal with the problem? Obviously Accelerometer class doesn't ask user to pass in a dispatcher, nor does it require that the client code use any of it's exposed function in ui thread (so CoreWindow.GetForCurrentThread() returns null, and I cannot get the dispatcher)

    Something more interesting is, I found that if I use Accelerometer in C#, the event is raised in worker thread (if in event handler I manipulate UI control, an exception will be thrown), but if I call the Accelerometer APIs in javascript, the event is raised in UI thread (I can directly change the dom in event handler).

    Could you explained more about this? Thanks a lot :-)

    Wednesday, March 27, 2013 4:57 AM