使用Task和FrameworkElement参数进行词法绑定

时间:2017-01-06 17:12:05

标签: c# wpf asynchronous

我根据Task中可用的外部lambda的参数获得了我对以下语句的期望但是,FrameworkElement强制转换对象的一些属性(包括Name)抛出了System.InvalidOperationException 。外部lambda中没有这样的错误。

Targets.AddHandler(CommandManager.ExecutedEvent, 
    (ExecutedRoutedEventHandler) ((s,e) => 
        Task.Run(() => 
            MessageBox.Show(string.Format("Targets {0} {1}", 
            ((FrameworkElement)s).Name, e.RoutedEvent.Name)))) 
    , true);

以下是((FrameworkElement)s).Name任务内部的观察部分......

enter image description here

我想这是一个线程问题? 错误的原因是什么?将我需要的值传递给任务的最佳方法是什么?

解决方案

正如@Mercer所解释的,我需要处理DependencyObject的线程亲和性。如答案中所述,它继承自DispatcherObject,所以 - 仅仅为了问题的完整性 - 如果有必要,我可以充分利用它来获取属性的实时值。

Targets.AddHandler(CommandManager.ExecutedEvent,
    (ExecutedRoutedEventHandler) (async (s, e) =>
            await Task.Run(() =>
            {
                var source = s as FrameworkElement;
                string elementName = "null";
                source.Dispatcher.Invoke(() =>
                    elementName = source.Name
                );

                MessageBox.Show(string.Format("Targets {0} {1}",
                    elementName, e.RoutedEvent.Name));
            })
    )
    , true
);

关于我在下面的评论中的问题,基于建议的阅读,我想等待一个任务是一个好主意的原因是与线程生命周期有关。
我尝试了上面的异步版本,在Task中调用了Thread.Sleep,并将其与async和await删除相同。它的表现相同。包含上述代码的窗口从主窗口启动,如果我启动任务并关闭子窗口,使主窗口保持运行,则MessageBox仍会在休眠期后显示。如果我也关闭了主机窗口,在任务完成之前,进程终止,MessageBox没有显示。
我不知道在任何一种情况下是否有任何内存泄漏,但我认为等待任务的做法是消除这种风险。

1 个答案:

答案 0 :(得分:1)

从后台线程(如Task运行的线程)中,您无法访问在应用程序的UI线程中创建的DependencyObjects的依赖项属性。这是因为DependencyObjects是DispatcherObjects,因此具有线程关联性。

使用Button Click处理程序的简单示例:

<Button Content="Click" Click="OnButtonClick"/>

以下Click处理程序方法抛出InvalidOperationException:

private async void OnButtonClick(object sender, RoutedEventArgs e)
{
    var button = (Button)sender;
    await Task.Run(() => MessageBox.Show((string)button.Content));
}

虽然这个不会:

private async void OnButtonClick(object sender, RoutedEventArgs e)
{
    var button = (Button)sender;
    var content = (string)button.Content;
    await Task.Run(() => MessageBox.Show(content));
}

我想在你的场景中也可以不等待任务:

private void OnButtonClick(object sender, RoutedEventArgs e)
{
    var button = (Button)sender;
    var content = (string)button.Content;
    Task.Run(() => MessageBox.Show(content));
}