winui3:从背景线程显示contentDialog。 coredispatcher.runasync不可用吗?

发布于 2025-01-24 09:03:04 字数 2395 浏览 1 评论 0 原文

我正在尝试显示一个winui对话框 - 即一个源于 contentDialog - 从背景线程执行的代码中,我需要等待对话框的结果。

我知道,只有通过某种调度程序将该代码返回给UI线程的某种调度程序才能显示对话框。

这是以当前形式显示我的对话框的方法:

public async Task<MyDialogResult> ShowMyDialogAsync(MyViewModel viewModel, string primaryButtonText, string closeButtonText)
{
    // EXCEPTION OCCURS HERE WHEN CALLING FROM NON UI-THREAD
    MyDialog dialog = new MyDialog(); // This class derives from ContentDialog

    // Set dialog properties
    dialog.PrimaryButtonText = primaryButtonText;
    dialog.CloseButtonText = closeButtonText;
    dialog.ViewModel = viewModel;
    dialog.XamlRoot = _xamlRoot;

    await dialog.ShowAsync();
    return dialog.ViewModel.MyDialogResult;
}

请注意,我需要此对话框的结果,该对话框由用户输入。这就是为什么该方法具有 task&lt; myDialogresult&gt; 的返回值的原因。

在进行研究时,我遇到了这个答案,它是指等待从背景线程发送的UI任务来自Microsoft。

他们在那里使用方法 runAsync 将转换为方法 runtaskAsync&lt; t&gt; 。 可以在 coredispatcher 类。

我的问题是:我无法尝试这种方法,因为访问它时的CoredisPatcher属性在运行时似乎总是为零(例如,从我的MainWindow类)。 这是运行时我的MainWindow实例的屏幕截图:

”在此处输入图像描述

根据文章,“调度程序”为Null似乎是一个设计选择。

唯一可用的类似属性是调度员。但这只有方法。 但是我不知道如何以我需要的方式使用它,这不仅是在UI线程上执行代码,而且还等待其完成,因此代码可以以受控的方式运行。

有人知道如何使用某种调度程序在Winui contentDialog 中显示如何显示方法并等待对话框的结果?

I am trying to show a WinUI dialog - i.e. a class that derives from ContentDialog - from code that executes from a background thread and I need to wait for the result of the dialog.

I know that showing a dialog from a background thread is only possible with some kind of dispatcher that dispatches this code back to the UI thread.

This is the method that shows my dialog in its current form:

public async Task<MyDialogResult> ShowMyDialogAsync(MyViewModel viewModel, string primaryButtonText, string closeButtonText)
{
    // EXCEPTION OCCURS HERE WHEN CALLING FROM NON UI-THREAD
    MyDialog dialog = new MyDialog(); // This class derives from ContentDialog

    // Set dialog properties
    dialog.PrimaryButtonText = primaryButtonText;
    dialog.CloseButtonText = closeButtonText;
    dialog.ViewModel = viewModel;
    dialog.XamlRoot = _xamlRoot;

    await dialog.ShowAsync();
    return dialog.ViewModel.MyDialogResult;
}

Please note that I need some result from this dialog which is entered by the user. That is why the method has a return value of Task<MyDialogResult>.

While doing my research I came across this answer, which refers to the article Await a UI task sent from a background thread from Microsoft.

There they use a method RunAsync which gets transformed into a method RunTaskAsync<T>.
The RunAsync method can be found on the CoreDispatcher class.

My problem is: I am unable to try this approach, because the CoreDispatcher property seems always to be null at runtime when accessing it (from my MainWindow class for example).
Here is a screenshot of my MainWindow instance during runtime:

enter image description here

According to this article, "Dispatcher" being null seems to be a design choice.

The only similar property available is a DispatcherQueue. But this only has the method TryEnqueue.
But I do not know how to use it in they way I need, which is not only to execute the code on the UI thread but also wait for its completion, so code can run after it in a controlled manner.

Does anyone know how an approach how to show in WinUI ContentDialog using some kind of dispatcher and wait for the result of the dialog?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

嘦怹 2025-01-31 09:03:05

我让它起作用。

第1部分

解决方案的第一部分是发现确实包含一个nuget软件包“ communitytoolkit.winui”,它确实包含一个类 dispatcherqueextensions 具有用于扩展方法的 类,在某些“ enqueueasync”方法中。

dispatcherqueueextensions文档是相同的。

使用正在使用的“ communityToolkit.winui” nuget软件包,您可以写下代码:

// Execute some asynchronous code
await dispatcherQueue.EnqueueAsync(async () =>
{
    await Task.Delay(100);
});

// Execute some asynchronous code that also returns a value
int someOtherValue = await dispatcherQueue.EnqueueAsync(async () =>
{
    await Task.Delay(100);

    return 42;
});

第2部分

第二部分重点是问题,即在背景线程上运行的代码必须不仅必须在上面调用代码UI线程,但也要等待其完成(因为在我的情况下需要对话框中的用户数据)。

我找到了一个问题“ runAsync-我如何等待UI线程上的工作完成? -i-await-the-the-the-ui-thread“> the-ui-thread”>在stackoveflow上。

此答案用户“ mike”介绍了一个解决此问题的类 dispatchertAskextensions
我将代码重写以使用类型 dispatcherqueue 而不是 coredispatcher (如我发现的那样,在Winui中不支持)。

/// <summary>
/// Extension methods for class <see cref="DispatcherQueue"/>.
///
/// See: https://stackoverflow.com/questions/19133660/runasync-how-do-i-await-the-completion-of-work-on-the-ui-thread?noredirect=1&lq=1
/// </summary>
public static class DispatcherQueueExtensions
{
    /// <summary>
    /// Runs the task within <paramref name="func"/> to completion using the given <paramref name="dispatcherQueue"/>.
    /// The task within <paramref name="func"/> has return type <typeparamref name="T"/>.
    /// </summary>
    public static async Task<T> RunTaskToCompletionAsync<T>(this DispatcherQueue dispatcherQueue,
        Func<Task<T>> func, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
    {
        var taskCompletionSource = new TaskCompletionSource<T>();
        await dispatcherQueue.EnqueueAsync(( async () =>
        {
            try
            {
                taskCompletionSource.SetResult(await func());
            }
            catch (Exception ex)
            {
                taskCompletionSource.SetException(ex);
            }
        }), priority);
        return await taskCompletionSource.Task;
    }

    /// <summary>
    /// Runs the task within <paramref name="func"/> to completion using the given <paramref name="dispatcherQueue"/>.
    /// The task within <paramref name="func"/> has return type void.
    /// 
    /// There is no TaskCompletionSource "void" so we use a bool that we throw away.
    /// </summary>
    public static async Task RunTaskToCompletionAsync(this DispatcherQueue dispatcherQueue,
        Func<Task> func, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal) =>
        await RunTaskToCompletionAsync(dispatcherQueue, async () => { await func(); return false; }, priority);
}

用法

此示例使用具有私有字段的类 _disPatcherqueue type dispatcherqueue 和一个私有字段 _xamlroot UIELEMENT 需要提前设置。

现在我们可以这样使用:

public async Task<MyDialogResult> ShowMyDialogAsync(MyViewModel viewModel, string primaryButtonText, string closeButtonText)
{
        // This code shows an dialog that the user must answer, the call must be dispatched to the UI thread
        // Do the UI work, and await for it to complete before continuing execution:
        var result = await _dispatcherQueue.RunTaskToCompletionAsync( async () =>
            {
               MyDialog dialog = new MyDialog(); // This class derives from ContentDialog

               // Set dialog properties
               dialog.PrimaryButtonText = primaryButtonText;
               dialog.CloseButtonText = closeButtonText;
               dialog.ViewModel = viewModel;
               dialog.XamlRoot = _xamlRoot;   
               await dialog.ShowAsync();

               // CODE WILL CONTINUE HERE AS SOON AS DIALOG IS CLOSED
               return dialog.ViewModel.MyDialogResult; // returns type MyDialogResult
            }
        );

        return result;
}

I got it working.

Part 1

The first part of the solution for me was to find out that there is a Nuget package "CommunityToolkit.WinUI" that does contain a class DispatcherQueueExtensions which has extension methods for the DispatcherQueue class, among those some "EnqueueAsync" methods.

This DispatcherQueueExtensions documentation refers to UWP, but the usage for WinUI is identical.

With the "CommunityToolkit.WinUI" Nuget package in use, you can write code like this:

// Execute some asynchronous code
await dispatcherQueue.EnqueueAsync(async () =>
{
    await Task.Delay(100);
});

// Execute some asynchronous code that also returns a value
int someOtherValue = await dispatcherQueue.EnqueueAsync(async () =>
{
    await Task.Delay(100);

    return 42;
});

Part 2

The second part focuses on the problem, that the code running on the background thread must no only invoke code on the UI thread, but also wait for its completion (because user data from a dialog is required in my case).

I found a question "RunAsync - How do I await the completion of work on the UI thread?" here on stackoveflow.

In this answer the user "Mike" introduces a class DispatcherTaskExtensions which solves this problem.
I rewrote the code to work with instances of type DispatcherQueue instead of CoreDispatcher (which is not supported in WinUI, as I discovered).

/// <summary>
/// Extension methods for class <see cref="DispatcherQueue"/>.
///
/// See: https://stackoverflow.com/questions/19133660/runasync-how-do-i-await-the-completion-of-work-on-the-ui-thread?noredirect=1&lq=1
/// </summary>
public static class DispatcherQueueExtensions
{
    /// <summary>
    /// Runs the task within <paramref name="func"/> to completion using the given <paramref name="dispatcherQueue"/>.
    /// The task within <paramref name="func"/> has return type <typeparamref name="T"/>.
    /// </summary>
    public static async Task<T> RunTaskToCompletionAsync<T>(this DispatcherQueue dispatcherQueue,
        Func<Task<T>> func, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
    {
        var taskCompletionSource = new TaskCompletionSource<T>();
        await dispatcherQueue.EnqueueAsync(( async () =>
        {
            try
            {
                taskCompletionSource.SetResult(await func());
            }
            catch (Exception ex)
            {
                taskCompletionSource.SetException(ex);
            }
        }), priority);
        return await taskCompletionSource.Task;
    }

    /// <summary>
    /// Runs the task within <paramref name="func"/> to completion using the given <paramref name="dispatcherQueue"/>.
    /// The task within <paramref name="func"/> has return type void.
    /// 
    /// There is no TaskCompletionSource "void" so we use a bool that we throw away.
    /// </summary>
    public static async Task RunTaskToCompletionAsync(this DispatcherQueue dispatcherQueue,
        Func<Task> func, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal) =>
        await RunTaskToCompletionAsync(dispatcherQueue, async () => { await func(); return false; }, priority);
}

Usage

This example uses a class that has a private field _dispatcherQueue of type DispatcherQueue and a private field _xamlRoot of type UIElement that need to be set in advance.

Now we can use this like so:

public async Task<MyDialogResult> ShowMyDialogAsync(MyViewModel viewModel, string primaryButtonText, string closeButtonText)
{
        // This code shows an dialog that the user must answer, the call must be dispatched to the UI thread
        // Do the UI work, and await for it to complete before continuing execution:
        var result = await _dispatcherQueue.RunTaskToCompletionAsync( async () =>
            {
               MyDialog dialog = new MyDialog(); // This class derives from ContentDialog

               // Set dialog properties
               dialog.PrimaryButtonText = primaryButtonText;
               dialog.CloseButtonText = closeButtonText;
               dialog.ViewModel = viewModel;
               dialog.XamlRoot = _xamlRoot;   
               await dialog.ShowAsync();

               // CODE WILL CONTINUE HERE AS SOON AS DIALOG IS CLOSED
               return dialog.ViewModel.MyDialogResult; // returns type MyDialogResult
            }
        );

        return result;
}
单挑你×的.吻 2025-01-31 09:03:05

正如您已经发现的那样,您应该能够在 communityToolkit.winui nuget软件包。

只需确保您调用 dispatcherqueue.getForCurrentThread()方法,以获取UI线程上 dispatcherqueue 的引用,并且您 specify a xamlroot contentdialog 一种或另一种方式。

以下是一个工作示例。

using CommunityToolkit.WinUI;
...

DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread();

await Task.Run(async () =>
{
    Thread.Sleep(3000); //do something on the background thread...

    int someValue = await dispatcherQueue.EnqueueAsync(async () =>
    {
        ContentDialog contentDialog = new ContentDialog()
        {
            Title ="title...",
            Content = "content...",
            CloseButtonText = "Close"
        };
        contentDialog.XamlRoot = (App.Current as App)?.m_window.Content.XamlRoot;
        await contentDialog.ShowAsync();

        return 1;
    });

    // continue doing doing something on the background thread...
});

As you have already discovered yourself, you should be able to use the DispatcherQueueExtensions in the CommunityToolkit.WinUI NuGet package.

Just make sure that you call the DispatcherQueue.GetForCurrentThread() method to get a reference to the DispatcherQueue on the UI thread and that you specify a XamlRoot for the ContentDialog one way or another.

Below is a working example.

using CommunityToolkit.WinUI;
...

DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread();

await Task.Run(async () =>
{
    Thread.Sleep(3000); //do something on the background thread...

    int someValue = await dispatcherQueue.EnqueueAsync(async () =>
    {
        ContentDialog contentDialog = new ContentDialog()
        {
            Title ="title...",
            Content = "content...",
            CloseButtonText = "Close"
        };
        contentDialog.XamlRoot = (App.Current as App)?.m_window.Content.XamlRoot;
        await contentDialog.ShowAsync();

        return 1;
    });

    // continue doing doing something on the background thread...
});
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文