-
-
Save smourier/d1961e2a8d18e762746cebe5948d36db to your computer and use it in GitHub Desktop.
using Microsoft.UI.Xaml; | |
namespace WinUIAppFx | |
{ | |
public partial class App : Application | |
{ | |
private Window m_window; | |
public App() | |
{ | |
InitializeComponent(); | |
} | |
protected override void OnLaunched(LaunchActivatedEventArgs args) | |
{ | |
if (m_window != null) // WindowsXamlManager.InitializeForCurrentThread will call this too, show the main only once | |
return; | |
m_window = new MainWindow(); | |
m_window.Activate(); | |
} | |
} | |
} |
<Window | |
x:Class="WinUIAppFx.MainWindow" | |
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> | |
<Grid> | |
<Button Click="Button_Click">click</Button> | |
</Grid> | |
</Window> |
using System; | |
using System.Runtime.Versioning; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.UI.Dispatching; | |
using Microsoft.UI.Xaml; | |
using Microsoft.UI.Xaml.Hosting; | |
[assembly: SupportedOSPlatform("windows10.0.17763.0")] | |
namespace WinUIAppFx | |
{ | |
public sealed partial class MainWindow : Window | |
{ | |
private MyOtherWindow _myOtherWindow; | |
public MainWindow() | |
{ | |
InitializeComponent(); | |
} | |
private void Button_Click(object sender, RoutedEventArgs e) | |
{ | |
if (_myOtherWindow != null) | |
return; | |
Title = "On thread " + Environment.CurrentManagedThreadId; | |
var thread = new Thread(state => | |
{ | |
// create a DispatcherQueue on this new thread | |
var dq = DispatcherQueueController.CreateOnCurrentThread(); | |
// initialize xaml in it | |
WindowsXamlManager.InitializeForCurrentThread(); | |
// create a new window | |
_myOtherWindow = new MyOtherWindow(); // some other Xaml window you've created | |
_myOtherWindow.AppWindow.Show(true); | |
// run message pump | |
dq.DispatcherQueue.RunEventLoop(); | |
}); | |
thread.IsBackground = true; // will be destroyed when main window is closed, behavior can be changed | |
thread.Start(); | |
// send some message to the second window to check it's handled from another thread | |
// note: real code should wait for _myOtherWindow to be fully initialized... | |
Task.Run(async () => | |
{ | |
for (var i = 0; i < 10; i++) | |
{ | |
await Task.Delay(1000); | |
_myOtherWindow.DispatcherQueue.TryEnqueue(() => | |
{ | |
_myOtherWindow.Title = "#" + i + " on thread " + Environment.CurrentManagedThreadId; | |
}); | |
} | |
}); | |
} | |
} | |
} |
I didn't need anything more than this when I tested it.
Try putting await Task.Delay(1)
before _myOtherWindow.Title = "#" + i + " on thread " + Environment.CurrentManagedThreadId;
. I believe you'll get a thread exception. You'll start on the second UI thread but after Task.Delay
you'll be on a background worker.
Everything was done and works by design, there are multiple non-STA threads indeed and there may be a race condition especially if you remove the 1000 sec wait, this is why there is a "real code should wait for _myOtherWindow to be fully initialized..." comment.
Do you understand what SynchronizationContext
is and its role in await
ing something on a UI thread? Trust me, you need my line or something like it, or else any UI code run on the 2nd window that includes async/await will break.
Or don't believe me, I'm fine either way.
Befpre
dq.DispatcherQueue.RunEventLoop();
you also need:SynchronizationContext.SetSynchronizationContext(new DispatcherQueueSynchronizationContext(dq.DispatcherQueue));
Otherwise any
await
on the second UI thread - for example an event handler - will resume on a worker thread. If any UI code is run after that it will throw an exception.