【问题标题】:SendMessageAsync throws exception randomly when communicating between Win32 and UWPWin32和UWP通信时SendMessageAsync随机抛出异常
【发布时间】:2018-04-09 14:50:39
【问题描述】:

在我的 UWP 应用中,我需要不断地从 WinForms (Win32) 组件向 UWP 应用发送数据,反之亦然。但是,我的 WinForms 组件中有一个奇怪的错误。有时,在启动 WinForm 时,我在调用 await connection.SendMessageAsync(message) 时收到 System.InvalidOperationException 说:A method was called at an unexpected time 其他时候,它运行良好。

我的代码:

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
}

方法是这样调用的:

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 设置为 250 毫秒。

是否有任何方法可以防止或正确处理此异常而不退出 WinForm 组件但仍建立重要的通信路径?

有什么想法吗?

谢谢

【问题讨论】:

  • 您的 Winforms 应用程序是否包含在与 UWP 应用程序相同的包中?您是否跟踪 ServiceClosed 事件(在 Winforms 进程中)和 TaskCanceled 事件(在 UWP 进程中)以了解连接消失?
  • 在上一次尝试完成 SendMessageAsync() 之前被您的 Tick 事件处理程序重新激活肯定不是很高兴。在应用程序启动或机器负载过重时,超过 250 毫秒的延迟并不少见。避免重入的一种简单方法是禁用计时器并在异步完成后重新启用它。或者使用 Task.Delay 而不是计时器。
  • @StefanWickMSFT 是的,两者都被跟踪并且它们在同一个包中
  • @StefanWickMSFT 感谢您提醒我有关 TaskCanceled 事件

标签: c# winforms uwp


【解决方案1】:

好的,我找到了解决方案。人们实际上可能称其为一种解决方法。

在我的 WinForm 中,我将代码更改如下:

AppServiceResponse serviceResponse = await connection.SendMessageAsync(message);

到:

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();
 }

我还更改了 App.xaml.cs 文件中的代码:

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

到:

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();
    }
}

我知道从技术上讲,我所做的只是重新启动表单直到它成功,这并不完全是解决它的正确方法。但是,它有效。

【讨论】:

  • 嗨@user9618595,如果这个或任何答案已经解决了您的问题,请点击复选标记考虑accepting it。这向更广泛的社区表明您找到了解决方案,并为回答者和您自己赢得了一些声誉。
【解决方案2】:

一些建议基于

参考Async/Await - Best Practices in Asynchronous Programming

避免使用async void,除非是事件处理程序。首选async Task 方法而不是async void 方法。

async void 方法会引发火灾和遗忘,这可能会导致遇到问题,因为没有在正确的上下文中引发异常。

Async void 方法具有不同的错误处理语义。当异步任务或异步任务方法抛出异常时,会捕获该异常并将其放置在任务对象上。对于 async void 方法,没有 Task 对象,因此从 async void 方法抛出的任何异常都将直接在 async void 方法启动时处于活动状态的 SynchronizationContext 上引发。

假设该方法在事件处理程序中被调用。然后重构方法以使用async Task

private async Task SendToUWPVoidAsync(object content) {

    //...

}

并将事件处理程序更新为异步

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
    }
}

这还应该允许捕获任何异常,如上例所示。

【讨论】:

    猜你喜欢
    • 2011-01-16
    • 2014-03-16
    • 2016-06-09
    • 1970-01-01
    • 1970-01-01
    • 2013-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多