【发布时间】:2011-06-07 10:00:11
【问题描述】:
我有一个 WPF(C# 和 .NET 4)应用程序,其中有一个长时间运行的任务,它阻塞了 UI,给人一种它已挂起的印象。我决定通过使用BackgroundWorker 线程将这个长时间运行的任务放到一个单独的线程中,并在一个单独的弹出窗口(下面命名为 WorkingDialog)中显示一个BusyIndicator。这工作正常,直到长时间运行的任务写入 BindingList(绑定到 UI 上的网格),我得到以下异常:
这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection
这是(非常精简的)业务层代码...
public class CustomMessage
{
public DateTime TimeStamp { get; private set; }
public string Message { get; private set; }
public CustomMessage(string message)
{
Message = message;
TimeStamp = DateTime.Now;
}
}
public class MyRandomBusinessClass
{
public BindingList<CustomMessage> LoggingList { get; private set; }
public MyRandomBusinessClass()
{
LoggingList = new BindingList<CustomMessage>();
}
public void SomeLongRunningTask()
{
System.Threading.Thread.Sleep(5000);
LoggingList.Add(new CustomMessage("Completed long task!"));
}
}
...用户界面...
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<StackPanel>
<DataGrid x:Name="logGrid"
AutoGenerateColumns="True"
ItemsSource="{Binding Path=LoggingList}" />
<Button Click="ProcessLongTask_Click"
Content="Do Long Task" />
</StackPanel>
</Window>
...用户界面代码...
using System.ComponentModel;
using System.Windows;
namespace WpfApplication4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MyRandomBusinessClass RandomBusinessClass { get; set; }
public MainWindow()
{
InitializeComponent();
RandomBusinessClass = new MyRandomBusinessClass();
logGrid.DataContext = RandomBusinessClass;
}
private void ProcessLongTask_Click(object sender, RoutedEventArgs e)
{
ProcessTask1();
}
private void ProcessTask1()
{
WorkingDialog workingDialog = new WorkingDialog();
workingDialog.Owner = this;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
RandomBusinessClass.SomeLongRunningTask();
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
workingDialog.Close();
};
worker.RunWorkerAsync();
workingDialog.ShowDialog();
}
}
}
...工人对话...
<Window xmlns:extToolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit/extended"
x:Class="WpfApplication4.WorkingDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Working Dialog"
WindowStartupLocation="CenterOwner"
Height="116"
Width="199">
<Grid>
<extToolkit:BusyIndicator x:Name="busyIndicator"
IsBusy="True"
BusyContent="Working ..."
HorizontalAlignment="Left"
VerticalAlignment="Top" />
</Grid>
</Window>
我在这个论坛中发现了几个线程指向Bea Stollnitz 的解决方法),但我想知道是否有一种方法可以执行长时间运行的任务,显示 WorkingDialog,然后在完成任务后关闭对话框使用任务并行库完成了吗?任务并行库能否在不修改业务层代码的情况下达到预期的效果?
编辑 - 解决方法 1
感谢 Ben 的 cmets,如果您的长时间运行的任务与 UI 位于同一程序集中或使用 System.Windows 命名空间,则有一种解决方法。使用当前的应用程序调度程序,我可以毫无例外地将消息添加到我的列表中:
public class MyRandomBusinessClass
{
public BindingList<CustomMessage> LoggingList { get; private set; }
public MyRandomBusinessClass()
{
LoggingList = new BindingList<CustomMessage>();
}
public void SomeLongRunningTask()
{
System.Threading.Thread.Sleep(5000);
Dispatcher myDispatcher = Application.Current.Dispatcher;
myDispatcher.BeginInvoke((Action)(() => LoggingList.Add(new CustomMessage("Completed long task!"))));
}
}
我仍然很好奇这是否可以使用任务并行库实现,还是我找错了树?!在我的实际应用程序中,长时间运行的类位于单独的类库中。
编辑 2
我接受了以下 Ben 的解决方案,因为它解决了我最初的问题,即在不阻塞 UI 或引发异常的情况下运行长时间运行的任务。我很欣赏最初的问题是关于任务并行库的,所以如果您在阅读本文时认为它具有 TPL 解决方案,请原谅我。
【问题讨论】:
-
您的 WorkingDialog 对这个故事至关重要吗?这听起来就像一个 Thread-to-GUI 问题。
-
嗨 Henk,我添加了 WorkerDialog xaml,它只显示 WPF 工具包中的 BusyIndicator。
标签: c# wpf multithreading c#-4.0 task-parallel-library