C# 在多窗口UWP应用程序中引发和处理事件
我创建了一些测试代码,这样我就可以试着找出如何在UWP中正确使用多个窗口。我想看看是否可以触发一个事件,并让多个窗口在事件处理程序中更新其UI。我终于找到了一个可行的方法,但我不完全确定它为什么有效 这是在我的页面中创建的类C# 在多窗口UWP应用程序中引发和处理事件,c#,windows,multithreading,uwp,multiple-views,C#,Windows,Multithreading,Uwp,Multiple Views,我创建了一些测试代码,这样我就可以试着找出如何在UWP中正确使用多个窗口。我想看看是否可以触发一个事件,并让多个窗口在事件处理程序中更新其UI。我终于找到了一个可行的方法,但我不完全确定它为什么有效 这是在我的页面中创建的类 public class NumberCruncher { private static Dictionary<int, Tuple<CoreDispatcher, NumberCruncher>> StaticDispatchers { g
public class NumberCruncher
{
private static Dictionary<int, Tuple<CoreDispatcher, NumberCruncher>> StaticDispatchers { get; set; }
static NumberCruncher()
{
StaticDispatchers = new Dictionary<int, Tuple<CoreDispatcher, NumberCruncher>>();
}
public NumberCruncher()
{
}
public event EventHandler<NumberEventArgs> NumberEvent;
public static void Register(int id, CoreDispatcher dispatcher, NumberCruncher numberCruncher)
{
StaticDispatchers.Add(id, new Tuple<CoreDispatcher, NumberCruncher>(dispatcher, numberCruncher));
}
public async Task SendInNumber(int id, int value)
{
foreach (var dispatcher in StaticDispatchers)
{
await dispatcher.Value.Item1.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Debug.WriteLine($"invoking {dispatcher.Key}");
dispatcher.Value.Item2.NumberEvent?.Invoke(null, new NumberEventArgs(id, value));
});
}
}
}
我有一个按钮,可以创建主页的新视图。然后我有另一个按钮调用SendInNumber()
方法
当我导航到主页时,我为窗口和NumberRuncher实例注册了Dispatcher。然后,在触发事件时,我使用该特定调度器的NumberRuncher EventHandler
这可以在不引发封送异常的情况下工作。如果我尝试使用当前类的EventHandler
await dispatcher.Value.Item1.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Debug.WriteLine($"invoking {dispatcher.Key}");
NumberEvent?.Invoke(null, new NumberEventArgs(id, value));
});
我在尝试将项添加到listView时遇到封送处理异常。但是,如果我在主页中维护SynchronizationContext,然后使用SynchronizationContext.Post更新listView。它很好用
SynchronizationContext synchronizationContext;
public MainPage()
{
this.InitializeComponent();
numberCruncher = new NumberCruncher();
numberCruncher.NumberEvent += NumberCruncher_NumberEvent;
synchronizationContext = SynchronizationContext.Current;
}
private async void NumberCruncher_NumberEvent(object sender, NumberEventArgs e)
{
synchronizationContext.Post(_ =>
{
listView.Items.Add($"{e.Id} sent {e.Number}");
}, null);
}
但是,这不起作用,并且在尝试更新listView时引发封送处理异常
private async void NumberCruncher_NumberEvent(object sender, NumberEventArgs e)
{
await CoreApplication.GetCurrentView().CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
listView.Items.Add($"{e.Id} sent {e.Number}");
});
}
这里发生了什么?需要记住的重要一点是,当触发事件时,订阅的方法将在与
Invoke
方法相同的线程上调用
如果尝试使用当前类的EventHandler
,则在尝试将该项添加到列表视图时会出现封送异常
发生第一个错误是因为您试图在另一个窗口的dispatcher(dispatcher.Value.Item1
)上触发当前类的事件。假设事件在窗口1上,并且dispatcher.Value.Item1
属于窗口2。进入Dispatcher
块后,您将启动窗口2的UI线程,并启动窗口1的NumberEvent
,NumberRuncher
将在窗口2的UI线程上运行窗口1的处理程序,从而导致异常
但是,这不起作用,并且在尝试更新listView
时引发封送处理异常
GetCurrentView()
方法返回当前活动的视图。因此,此时处于活动状态的应用程序视图都将返回。在您的情况下,它将是您单击按钮的那个。如果在目标窗口的UI线程上调用Invoke
方法,则在NumberEvent
处理程序中不需要任何额外的代码。GetCurrentView返回指向活动窗口的指针,而不一定是包含试图更新的listView实例的窗口。因此,我认为您最终使用了错误的UI线程(因为每个窗口都有自己的UI线程)。您尝试过使用“listView.Dispatcher”吗?这应该将调用封送到此listView实例的右UI线程。文档说GetCurrentView返回活动视图,但我设置了一个线程,将ApplicationView.GetForCurrentView().Id和CoreApplication.GetCurrentView()的状态打印到调试控制台。当我有两个窗口时,每个窗口的输出是不同的。这让我相信GetCurrentView()返回的是该UI线程的视图,而不是活动窗口。是的,但是您的事件是否会在UI线程上触发?如果是这样,那么您首先就不需要调度器。您似乎需要它这一事实表明事件不会在与您尝试更新的ListView相对应的UI线程上触发。如果只执行“listView.Dispatcher.RunAsync”,会发生什么?(而不是CoreApp.GetCurrentView().CoreWin.Dispatcher.RunAsync)如果我使用listView调度器,我会在listView中为我打开的每个窗口收到一条消息,这样我会看到-23473在窗口-23473上发送123
两次,打开两个窗口。这就是我所认为的情况。我还猜测,存储SynchronizationContext然后使用它来更新UI是有效的,因为我使用UI线程来更新UI。SynchronizationContext
有效的原因是您将其存储在页面构造函数中,该构造函数必须在UI线程上运行,因此上下文与正确的UI线程关联:-)
private async void NumberCruncher_NumberEvent(object sender, NumberEventArgs e)
{
await CoreApplication.GetCurrentView().CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
listView.Items.Add($"{e.Id} sent {e.Number}");
});
}