У меня есть приложение WPF (C # и .NET 4), в котором выполняется долго выполняемая задача, блокирующая пользовательский интерфейс, и создается впечатление, что оно зависло.Я решил поместить эту длительную задачу в отдельный поток с помощью потока BackgroundWorker
и показал BusyIndicator
в отдельном всплывающем окне (ниже названо WorkingDialog).Это работало нормально, пока долго выполняющаяся задача не записывает в BindingList (который привязан к сетке в пользовательском интерфейсе), и я получил следующее исключение:
Этот тип CollectionView не поддерживает изменения в егоSourceCollection из потока, отличного от потока Dispatcher
Это (очень урезанная версия) код бизнес-уровня ...
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
Благодаря комментариям Бена ниже, естьОбходной путь, если ваша долгосрочная задача находится в той же сборке, что и пользовательский интерфейс, или использует пространство имен 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!"))));
}
}
Мне все еще интересно, возможно ли это с помощью библиотеки параллельных задач или я лаю неправильное дерево?!В моем реальном приложении долгоиграющий класс находится в отдельной библиотеке классов.
Edit 2
Я принял решение Бена ниже, поскольку оно решает мою начальную проблемузапуск долгосрочной задачи без блокировки пользовательского интерфейса или исключения.Я ценю, что первоначальный вопрос был о параллельной библиотеке задач, так что извините, если вы читаете эту мысль, что у нее есть решение TPL.