Отображение окна в отдельном потоке работает только один раз

#c# #wpf #multithreading

#c# #wpf #многопоточность

Вопрос:

Я использую следующий код, чтобы попытаться отобразить окно wpf в отдельном потоке, чтобы анимированный GIF в нем работал, пока поток пользовательского интерфейса главного окна обрабатывает данные:

 private Thread tBusy;


private void ShowBusyWindow(string message, double top, double left, double height, double width)
{
    BusySplash busyForm = new BusySplash(message, top, left, height, width)
    busyForm.Show();
}

private void ShowBusy(string message, UIElement container)
{
    if (busy != null) return;

    double top = container.PointToScreen(new Point(0, 0)).Y;
    double left = container.PointToScreen(new Point(0, 0)).X;
    double width = container.RenderSize.Width;
    double height = container.RenderSize.Height;


    ThreadStart ts = new ThreadStart(() => ShowBusyWindow(message, top, left, height, width));
    tBusy = new Thread(ts);

    tBusy.SetApartmentState(ApartmentState.STA);
    tBusy.IsBackground = true;
    tBusy.Start();
}

private void HideBusy()
{

    tBusy.Abort();
    tBusy = null;
}
  

Я окружаю код, выполняющий работу с функцией ShowBusy() в начале и HideBusy() в конце.

Но, к сожалению, ShowBusy() запускается один раз успешно, а затем выдает:

Система.Исключение InvalidOperationException: ‘Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку.’

Что я могу сделать, чтобы предотвратить эту ошибку? Я попытался выполнить busyForm.Show() с помощью диспетчера busyForm, но получаю ту же ошибку.

Комментарии:

1. Использование нескольких потоков пользовательского интерфейса в wpf возможно, но это выбор последнего средства. Гораздо привычнее использовать анимацию wpf для счетчика в потоке пользовательского интерфейса. Соберите данные в фоновом потоке и передайте их обратно в поток пользовательского интерфейса для отображения. Рассматривали ли вы этот подход?

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

3. Никогда не вызывайте Thread.Abort() (если только вы не пытаетесь полностью завершить работу своего приложения), поскольку это может привести к повреждению времени выполнения, и вы не сможете доверять тому, что какой-либо код впоследствии будет работать должным образом.

Ответ №1:

Работа пользовательского интерфейса должна быть выгружена в поток пользовательского интерфейса.

 private void ShowBusyWindow(string message, double top, double left, double height, double width)
{
    Application.Current.Dispatcher.Invoke(() => {
        BusySplash busyForm = new BusySplash(message, top, left, height, width)
        busyForm.Show();
    });
}
  

Однако есть несколько вещей:

  • Вам действительно не нужно создавать отдельный поток STA для вашего индикатора занятости. Перенесите работу, отличную от пользовательского интерфейса, на задачу, чтобы пользовательский интерфейс не блокировался. Оттуда делайте обновления пользовательского интерфейса только в потоке пользовательского интерфейса. Похоже, причина, по которой ваш пользовательский интерфейс блокируется, заключается в том, что вы слишком много делаете с пользовательским интерфейсом.
  • Прерывание потока для закрытия индикатора занятости немного странно. Намного проще просто вызвать.Закройте элемент управления (при условии, что это окно WPF).