Можно ли запустить долгосрочный метод и диалоговое окно «Работать ...» с помощью библиотеки параллельных задач, чтобы разрешить запись длинной задачи в BindingList? - PullRequest
2 голосов
/ 07 июня 2011

У меня есть приложение 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.

1 Ответ

1 голос
/ 07 июня 2011

Вам нужно использовать dispatcher , чтобы добавить новый элемент в ваш список регистрации.

public void SomeLongRunningTask()
{
    System.Threading.Thread.Sleep(5000);

    Application.Current.Dispatcher.BeginInvoke((Action)(() => LoggingList.Add(new CustomMessage("Completed long task!"))));
}

Редактировать :

public class MyRandomBusinessClass
{
  public BindingList<CustomMessage> LoggingList { get; private set; }
  public Action<MyRandomBusinessClass, CustomMessage> CallBackAction { get; private set; }

  public MyRandomBusinessClass(Action<MyRandomBusinessClass, CustomMessage> cba) 
  {
      LoggingList = new BindingList<CustomMessage>();
      this.CallBackAction = cba;
  }

  public void SomeLongRunningTask()
  {
    System.Threading.Thread.Sleep(5000);

    if (cba != null)
      CallBackAction(this, new CustomMessage("Completed long task!"));
  }
}

Тогда, где вы создаете свой бизнес-класс:

MyRandomBusinessClass businessClass = new MyRandomBusinessClass((sender, msg) =>
   Application.Current.Dispatcher.BeginInvoke((Action)(() => 
      sender.LoggingList.Add(msg))));
...