WPF C# - редактирование списка из другого потока

Я понимаю, что я делаю, вероятно, довольно глупо, но я в середине обучения WPF и хотел бы знать, как это сделать.

У меня есть окно со списком на нем. Listbox используется для доставки сообщений о состоянии программы во время ее работы. Например, "сервер запущен", "новое соединение на IP #" и т. д. Я хотел, чтобы это постоянно обновлялось в фоновом режиме, поэтому я создал новый поток для обработки обновления этого, но когда я сделал вызов, чтобы добавить элемент Я получаю сообщение об ошибке "вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет."

любая идея, как я могу обновить список из другого потока? Или на заднем плане и т. д.

3 ответов



обновление

если вы используете C# 5 и .NET 4.5 или выше, вы можете избежать попадания в другой поток в первую очередь с помощью async и await, например:

private async Task<string> SimLongRunningProcessAsync()
{
    await Task.Delay(2000);
    return "Success";
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    button.Content = "Running...";
    var result = await SimLongRunningProcessAsync();
    button.Content = result;
}

просто:

Dispatcher.BeginInvoke(new Action(delegate() 
  {
     myListBox.Items.Add("new item"));
  }));

если вы находитесь в код-позади. В противном случае вы можете получить доступ к диспетчеру (который находится на каждом UIElement) использование:

Application.Current.MainWindow.Dispatcher.BeginInvoke(...

хорошо, что много в одной строке, позвольте мне перейти к нему:

когда вы хотите обновление элемента управления пользовательского интерфейса вы, как говорится в сообщении, должны сделать это из потока пользовательского интерфейса. Существует встроенный способ передачи делегата (метода) в поток пользовательского интерфейса:Dispatcher. Как только у вас есть Dispatcher вы можете Invoke() of BeginInvoke() передача делегата для запуска в потоке пользовательского интерфейса. Единственная разница -Invoke() вернется только после запуска делегата (т. е. в вашем случае добавлен новый элемент ListBox), тогда как BeginInvoke() немедленно вернется, поэтому ваш другой поток, из которого вы звоните может продолжаться (диспетчер будет запускать ваш делегат как можно скорее, что, вероятно, будет прямо сейчас).

я передал анонимный делегат выше:

delegate() {myListBox.Items.Add("new item");}

бит между {} является блоком метода. Это называется анонимным, потому что создается только один, и у него нет имени (обычно вы можете сделать это с помощью лямбда-выражения, но в этом случае C# не может разрешить вызов метода BeginInvoke ()). Или я мог бы создать экземпляр делегат:

Action myDelegate = new Action(UpdateListMethod);

void UpdateListMethod() 
{
  myListBox.Items.Add("new item");
}

затем передал, что:

Dispatcher.Invoke(myDelegate);

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


вы также можете использовать делегат action с анонимными методами для обновления основного потока из рабочего потока. Для получения дополнительной информации о классе действий вы можете посмотреть здесь:

http://msdn.microsoft.com/en-us/library/018hxwa8.aspx

Если вы хотите обновить listbox из нескольких точек, я предлагаю эксплицитно установить делегат, однако, если вы просто хотите обновить поток в одной точке в методе с одним вызовом, он может сделайте следующее:

        listbox.Dispatcher.BeginInvoke(new Action(delegate()
        {
            listbox.Items.Add(item); //where item is the item to be added and listbox is the control being updated.
        }));

обратите внимание, что я использую класс Action, поскольку он инкапсулирует метод, который имеет один параметр (в данном случае listItem).


вы захотите использовать диспетчер.Метод BeginInvoke. Например, если listbox имеет имя listbox

  // On worker thread
  listbox.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
  new Action(delegate() { listbox.Items.Add("Server started") });

это будет очередь делегата для выполнения в потоке пользовательского интерфейса, который listbox принадлежит.