简体   繁体   English

Win32和UWP之间进行通信时,SendMessageAsync随机引发异常

[英]SendMessageAsync throws exception randomly when communicating between Win32 and UWP

In my UWP App I need to continuously send data to a UWP app from a WinForms (Win32) component and vice versa. 在我的UWP应用中,我需要不断地从WinForms(Win32)组件向UWP应用发送数据,反之亦然。 However, I have a weird bug in my WinForms component. 但是,我的WinForms组件中有一个奇怪的错误。 Sometimes, upon launching the WinForm, I get a System.InvalidOperationException when calling await connection.SendMessageAsync(message) saying: A method was called at an unexpected time Other times, it works perfectly. 有时,启动WinForm时,我在调用await connection.SendMessageAsync(message)时收到System.InvalidOperationException说法: A method was called at an unexpected time

My Code: 我的代码:

private async void SendToUWPVoidAsync(object content)
{
    ValueSet message = new ValueSet();
    if (content != "request") message.Add("content", content);
    else message.Add(content as string, "");

    #region SendToUWP

    // if connection isn't inited
    if (connection == null)
    {
        // init
        connection = new AppServiceConnection();
        connection.PackageFamilyName = Package.Current.Id.FamilyName;
        connection.AppServiceName = "NotifyIconsUWP";
        connection.ServiceClosed += Connection_ServiceClosed;

        // attempt connection 
        AppServiceConnectionStatus connectionStatus = await connection.OpenAsync();
    }

    AppServiceResponse serviceResponse = await connection.SendMessageAsync(message);

    // get response
    if (serviceResponse.Message.ContainsKey("content"))
    {
        object newMessage = null;
        serviceResponse.Message.TryGetValue("content", out newMessage);

        // if message is an int[]
        if (newMessage is int[])
        {
            // init field vars
            int indexInArray = 0;
            foreach (int trueorfalse in (int[])newMessage)
            {
                // set bool state based on index
                switch (indexInArray)
                {
                    case 0:
                        notifyIcon1.Visible = Convert.ToBoolean(trueorfalse);
                        break;
                    case 1:
                        notifyIcon2.Visible = Convert.ToBoolean(trueorfalse);
                        break;
                    case 2:
                        notifyIcon3.Visible = Convert.ToBoolean(trueorfalse);
                        break;
                    default:
                        break;
                }
                indexInArray++;
            }
        }
    }
    #endregion
}

The method is called like this: 该方法的调用方式如下:

private void TCheckLockedKeys_Tick(object sender, EventArgs e)
{
    ...

    if (statusesChanged)
    {
        // update all bools
        bool1 = ...;
        bool2 = ...;
        bool3 = ...;

        // build int[] from bool values
        int[] statuses = new int[] { Convert.ToInt32(bool1), Convert.ToInt32(bool2), Convert.ToInt32(bool3) };

        // update UWP sibling
        SendToUWPVoidAsync(statuses);
    }

    // ask for new settings
    SendToUWPVoidAsync("request");
}

TCheckLockedKeys_Tick.Interval is set to 250 milliseconds. TCheckLockedKeys_Tick.Interval设置为250毫秒。

Is there any way to prevent or to correctly handle this exception without the WinForm Component exiting but still establishing the vital communication path? 在WinForm组件不退出但仍建立重要的通信路径的情况下,是否有任何方法可以防止或正确处理此异常?

Any ideas? 有任何想法吗?

Thanks 谢谢

Okay, I have found a solution. 好的,我找到了解决方案。 One might actually call it a workaround. 有人可能会称其为解决方法。

In my WinForm, I changed the code as follows: 在WinForm中,我将代码更改如下:

AppServiceResponse serviceResponse = await connection.SendMessageAsync(message);

to: 至:

AppServiceResponse serviceResponse = null;
try
{
    // send message
    serviceResponse = await connection.SendMessageAsync(message);
}
catch (Exception)
{
     // exit 
     capsLockStatusNI.Visible = false;
     numLockStatusNI.Visible = false;
     scrollLockStatusNI.Visible = false;

     Application.Exit();
 }

I have also changed code in my App.xaml.cs file: 我还更改了App.xaml.cs文件中的代码:

private async void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
    if (this.appServiceDeferral != null)
    {
        // Complete the service deferral.
        this.appServiceDeferral.Complete();
    }
}

to: 至:

private async void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
    if (reason == BackgroundTaskCancellationReason.SystemPolicy)
    {
        // WinForm called Application.Exit()
        await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
    }
    if (this.appServiceDeferral != null)
    {
        // Complete the service deferral.
        this.appServiceDeferral.Complete();
    }
}

I know all I'm doing is, technically, relaunching the Form till it succeeds which is not entirely the correct way of solving it. 我知道我从技术上来说只是重新启动Form,直到成功为止,这并不是解决它的正确方法。 But, it works. 但是,它有效。

Some advice based on 一些基于的建议

Reference Async/Await - Best Practices in Asynchronous Programming 参考Async / Await-异步编程最佳实践

Avoid using async void except with event handlers. 除事件处理程序外,避免使用async void Prefer async Task methods over async void methods. async void方法相比,首选async Task方法。

async void methods a fire an forget, which can cause the issues being encountered as exceptions are not being thrown in the correct context. 异步void方法会引起麻烦,这可能会导致遇到问题,因为没有在正确的上下文中引发异常。

Async void methods have different error-handling semantics. 异步void方法具有不同的错误处理语义。 When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. 从异步Task或异步Task方法抛出异常时,将捕获该异常并将其放置在Task对象上。 With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started. 使用异步void方法时,没有Task对象,因此从异步void方法抛出的任何异常都将直接在启动异步void方法时处于活动状态的SynchronizationContext上引发。

Given that the method is being called within an event handler. 假定在事件处理程序中调用了该方法。 then refactor the method to use an async Task 然后重构该方法以使用async Task

private async Task SendToUWPVoidAsync(object content) {

    //...

}

and update the event handler to be async 并将事件处理程序更新为异步

private async void TCheckLockedKeys_Tick(object sender, EventArgs e) {
    try {
        //...

        if (statusesChanged) {
            // update all bools
            bool1 = ...;
            bool2 = ...;
            bool3 = ...;

            // build int[] from bool values
            int[] statuses = new int[] { Convert.ToInt32(bool1), Convert.ToInt32(bool2), Convert.ToInt32(bool3) };

            // update UWP sibling
            await SendToUWPVoidAsync(statuses);
        }

        // ask for new settings
        await SendToUWPVoidAsync("request");

    }catch {
        //...handle error appropriately
    }
}

which should also allow for any exception to be caught as shown in the example above. 如上例所示,这还应允许捕获任何异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM