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

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

У меня есть окно со списком на нем. Список используется для доставки сообщений о состоянии программы во время ее работы. Например, "Сервер запущен", "Новое соединение с 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() из 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);

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

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

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

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

        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).

Вы хотите использовать Dispatcher.BeginInvoke. Например, если список назван listbox

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

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

Другие вопросы по тегам