WinRT - 在我的自定义类中使用MessageDialog.ShowAsync会抛出UnauthorizedAccessException异常

11

我想编写自己的控件,当调用构造函数时,会显示一个消息框。

public class Class1
{
    public Class1()
    {
        ShowDialog();
    }
    void ShowDialog()
    {
        SynchronizationContext context = SynchronizationContext.Current;
        if (context != null)
        {
            context.Post((f) =>
            {
                MessageDialog dialog = new MessageDialog("Hello!");
                dialog.ShowAsync();
            }, null);
        }
    }
}

如果我的类被他人使用,并且编写的代码如下所示,在dialog.ShowAsync();中总是会抛出UnauthorizedAccessException异常。
void MainPage_Loaded(object sender, RoutedEventArgs e)
        {

            ClassLibrary1.Class1 c1 = new ClassLibrary1.Class1();
            MessageDialog dialog1 = new MessageDialog("");
            dialog1.ShowAsync();
        }

有没有一种方法可以在不抛出异常的情况下显示消息对话框?


我找到了一种方法,享用吧!

Task ShowDialog()
        {
            CoreDispatcher dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
            Func<object, Task<bool>> action = null;
            action = async (o) =>
            {
                try
                {
                    if (dispatcher.HasThreadAccess)
                        await new MessageDialog("Hello!").ShowAsync();
                    else
                    {
                        dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                        () => action(o));
                    }
                    return true;
                }
                catch (UnauthorizedAccessException)
                {
                    if (action != null)
                    {
                        Task.Delay(500).ContinueWith(async t => await action(o));
                    }
                }
                return false;
            };
            return action(null);
        }

1
好吧,那很痛苦。当UI已经显示消息框时,您无法显示消息框。这需要进行互锁,当然非常难做。没有真正的解决方法,但可以避免工作线程自行推送框。 - Hans Passant
我找到了一种方法,只需修改 ShowDialog 方法即可解决问题。 - glover
那不是真正的解决方法。如果调用ShowAsync()的代码不是你编写的,会发生什么呢?仍然会崩溃。 - Hans Passant
我写了ShowAsync()。await new MessageDialog("Hello!").ShowAsync(); 它可以解决这个问题。 - glover
@Hans Passant,我正在尝试在下载完成后显示一个消息框。不应该抛出异常,因为它没有线程访问权限或其他原因。这不是异步的吗?但是我现在看不到它是异步的了。在遇到这个问题之后。 - RelativeGames
使消息框异步化是非常不必要的。如此邪恶和不必要,以至于他们可能只有一个可能的目标在心中。那就是阻止你使用它。这是公平的,因为它是一个可怕的UI小工具。 - Hans Passant
4个回答

3

由于MessageDialogue需要在UI线程上运行,你可以尝试将其切换到:

var dispatcher = Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher;
dispatcher.RunAsync(DispatcherPriority.Normal, <lambda for your code which should run on the UI thread>);

它不起作用。实际上,效果与SynchronizationContext.Post或Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync相同。它们都是用于在UI线程中显示对话框的。 - glover

1
清晰的解决方案可能如下所示。有趣的部分隐藏在showDialogAsync()中。为了方便,您可以使用Close()方法以编程方式再次关闭当前对话框。IUIDispatcher是另一个帮助界面,您可以轻松地重建它自己:
    private readonly IUIDispatcher _dispatcher;
    readonly Object _queueMonitor = new object();
    readonly Object _showMonitor = new object();
    private IAsyncOperation<IUICommand> _currentDialogOperation;
    readonly Queue<MessageDialog> _dialogQueue = new Queue<MessageDialog>();


    public async Task ShowAsync(string content)
    {
        var md = new MessageDialog(content);
        await showDialogAsync(md);
    }

    public async Task ShowAsync(string content, string caption)
    {
        var md = new MessageDialog(content, caption);
        await showDialogAsync(md);
    }

    public async Task<MessageDialogResult> ShowAsync(string content, MessageDialogType dialogType)
    {
        var messageDialogResult = await ShowAsync(content, null, dialogType);
        return messageDialogResult;
    }

    public async Task<MessageDialogResult> ShowAsync(string content, string caption, MessageDialogType dialogType)
    {
        var result = MessageDialogResult.Ok;


            var md = string.IsNullOrEmpty(caption) ?  new MessageDialog(content) : new MessageDialog(content, caption);

        switch (dialogType)
        {
            case MessageDialogType.Ok:
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
                md.CancelCommandIndex = 0;
                md.DefaultCommandIndex = 0;
                break;
            case MessageDialogType.OkCancel:
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
                md.DefaultCommandIndex = 0;
                md.CancelCommandIndex = 1;
                break;
            case MessageDialogType.YesNo:
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
                  md.DefaultCommandIndex = 0;
                md.CancelCommandIndex = 1;
                break;
            case MessageDialogType.YesNoCancel:
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
                md.DefaultCommandIndex = 0;
                md.CancelCommandIndex = 1;
                break;
            default:
                throw new ArgumentOutOfRangeException("dialogType");
        }

        await showDialogAsync(md);

        return result;
    }


    /// <summary>
    /// Shows the dialogs, queued and one after the other.
    /// We need this as a workaround for the the UnauthorizedAcsess exception. 
    /// </summary>
    /// <param name="messageDialog">The message dialog.</param>
    /// <returns></returns>
    async Task showDialogAsync(MessageDialog messageDialog)
    {
        //Calls this function in a separated task to avoid ui thread deadlocks.
        await Task.Run(async () => 
        { 
            lock (_queueMonitor)
            {
                _dialogQueue.Enqueue(messageDialog);
            }
            try
            {
                while (true)
                {
                    MessageDialog nextMessageDialog;
                    lock (_queueMonitor)
                    {
                        if (_dialogQueue.Count > 1)
                        {
                            Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Next dialog is waiting for MessageDialog to be accessable!!");
                            Monitor.Wait(_queueMonitor); //unlock and wait - regains lock after waiting
                        }

                        nextMessageDialog = _dialogQueue.Peek();
                    }

                    var showing = false;
                    _dispatcher.Execute(async () =>  
                    {
                        try
                        {
                            lock (_showMonitor)
                            {
                                showing = true;
                                _currentDialogOperation = nextMessageDialog.ShowAsync();
                            }

                            await _currentDialogOperation;

                            lock (_showMonitor)
                                _currentDialogOperation = null;
                        }
                        catch (Exception e)
                        {
                            Debug.WriteLine("MessageDialogService.cs | showDialogAsync | " + e);
                        }
                        lock (_showMonitor)
                        {
                            showing = false;
                            Monitor.Pulse(_showMonitor); //unlock and wait - regains lock after waiting
                        }
                    });


                    lock (_showMonitor)
                    {
                        if (showing)
                        {
                            Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Waiting for MessageDialog to be closed!!");
                            //we must wait here manually for the closing of the dialog, because the dispatcher does not return a waitable task.
                            Monitor.Wait(_showMonitor); //unlock and wait - regains lock after waiting
                        }
                    }
                    Debug.WriteLine("MessageDialogService.cs | showDialogAsync | MessageDialog  was closed.");
                    return true;
                }
            }
            finally
            {
                //make sure we leave this in a clean state
                lock (_queueMonitor)
                {
                    _dialogQueue.Dequeue();
                    Monitor.Pulse(_queueMonitor);
                }
            }
        });
    }


    public void Close(string keyContent="")
    {
        try
        {
            if (keyContent.IsNullOrEmpty())
            {
                lock (_showMonitor)
                {
                    if (_currentDialogOperation == null) return;
                    _currentDialogOperation.Cancel();
                    _currentDialogOperation = null;
                }
            }
            else
            {
                var cancel = false;
                lock (_queueMonitor)
                {
                    if (_dialogQueue.Count == 0)
                        return;

                    var currentDialog = _dialogQueue.Peek();

                    Debug.WriteLine("MessageDialogService.cs | Close | {0}", currentDialog.Content);
                    if (currentDialog.Content == keyContent)
                    {
                        cancel = true;
                    }
                }
                if (!cancel) return;
                lock (_showMonitor)
                {
                    if (_currentDialogOperation == null) return;
                    _currentDialogOperation.Cancel();
                    _currentDialogOperation = null;
                }
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine("MessageDialogService.cs | Close | " + e);
        }

    }

0

我想我找到了答案。当在主线程以外的任何其他线程中创建消息框时,我遇到了同样的问题。 这是C++的解决方案,但我认为你可以很容易地将其转换;)

    IAsyncOperation<IUICommand^> ^Op = msgbox->ShowAsync();
    task<IUICommand^>( Op ).then([=](IUICommand^ C)
    {

    }).then([](task<void> t) 
        {
            try 
            {
                t.get();
            }
            catch (Platform::Exception ^e)
            {   
                //ERROR!                            
            }           
        });

顺便提一下,这是处理任何WinRT/Windows 8 Store C++异常的正确方式。


0

您可以随时使用

Execute.OnUIThread( async () => {
...
var dialog = new MessageDialog(yourMessage);
await dialog.ShowAsync();
...
});

这并不能解决UI中的竞态条件问题,如果你正在尝试从后台线程启动多个对话框。但是你可以使用try/catch来确保你覆盖了那种情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接