Могу ли я создать поток, который может изменять пользовательский интерфейс и который я могу прервать?

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

Однако для безопасного изменения элементов управления в моей форме мне нужно использовать методы Invoke или BeginInvoke формы. Если я это сделаю, то смогу объединить все свои операции с пользовательским интерфейсом в одну функцию, например:

private delegate void DoUIStuffDelegate(Thing1 arg1, Thing2 arg2);
private void doUIStuff(Thing1 arg1, Thing2 arg2)
{
    control1.Visible = false;
    this.Controls.Add(arg1.ToButton());
    ...
    control100.Text = arg2.ToString();
}

...

private void backgroundThread()
{
    Thing1 arg1 = new Thing1();
    Thing2 arg2 = new Thing2();

    this.Invoke(new DoUIStuffDelegate(doUIStuff), arg1, arg2);
}

Thread uiStuffThread = null;

public void OnEventFired()
{
    if (uiStuffThread != null)
        uiStuffThread.Abort();

    uiStuffThread = new Thread(backgroundThread);
    uiStuffThread.Start();
}

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

private delegate void DoUIStuffLine1Delegate();
private delegate void DoUIStuffLine2Delegate(Thing1 arg1);
...

private delegate void DoUIStuffLine100Delegate(Thing2 arg2);

private void doUIStuffLine1()
{
    control1.Visible = false;
}

private void doUIStuffLine2()
{
    this.Controls.Add(arg1.ToButton());
}

...

private void doUIStuffLine100(Thing2 arg2)
{
    control100.Text = arg2.ToString();
}

...

private void backgroundThread()
{
    Thing1 arg1 = new Thing1();
    Thing2 arg2 = new Thing2();

    this.Invoke(new DoUIStuffLine1Delegate(doUIStuffLine1));
    this.Invoke(new DoUIStuffLine2Delegate(doUIStuffLine2), arg1);
    ...
    this.Invoke(new DoUIStuffLine100Delegate(doUIStuffLine100), arg2);
}

Thread uiStuffThread = null;

public void OnEventFired()
{
    if (uiStuffThread != null)
        uiStuffThread.Abort();

    uiStuffThread = new Thread(backgroundThread);
    uiStuffThread.Start();
}

но это ужасный, неразрешимый беспорядок. Есть ли способ создать поток, который может изменять пользовательский интерфейс и который я могу прервать? Чтобы я мог сделать что-то вроде этого:

private void doUIStuff()
{
    Thing1 arg1 = new Thing1();
    Thing2 arg2 = new Thing2();

    control1.Visible = false;
    this.Controls.Add(arg1.ToButton());
    ...
    control100.Text = arg2.ToString();
}

Thread uiStuffThread = null;

public void OnEventFired()
{
    if (uiStuffThread != null)
        uiStuffThread.Abort();

    uiStuffThread = this.GetNiceThread(doUIStuff);
    uiStuffThread.Start();
}

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
1 200
4

Ответы 4

Во-первых - не отключайте проверки между потоками ... формы имеют сходство потоков ...

Во-вторых, старайтесь не прерывать потоки; это нехорошо - вы должны предпочесть чистое завершение работы (например, отмену, которую поддерживает BackgroundWorker)

Один из вариантов - написать метод-оболочку, который:

  • принимает типизированный делегат (так что вы можете называть его проще)
  • выполняет необходимую проверку (генерирует исключение для завершения и развертывания)

Например:

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        try {
          Action<Action> update = thingToDo =>
          {
              if (worker.CancellationPending) throw new SomeException();
              this.Invoke(thingToDo);
          };

          //...
          string tmp = "abc"; // long running
          update(() => this.Text = tmp);

          tmp = "def"; // long running
          update(() => textbox1.Text = tmp);
        } catch (SomeException) {
          e.Cancel = true;
        }
    }

Это все еще немного беспорядочно, но, возможно, чище, чем прерывание потоков слева и справа ...

Вы можете избежать использования Invoke, используя объект SynchronizationContext, который был введен во фреймворке 2. Хорошо, это то же самое, вы заменяете одно на другое, но на самом деле это более эффективно и надежно. В любом случае, кросс-потокам нужны проверки, потому что без этого вы никогда не сможете получить доступ к элементу управления, созданному в другом потоке.

Об этом читайте в: http://www.codeproject.com/KB/cpp/SyncContextTutorial.aspxhttp://codingly.com/2008/08/04/invokerequired-invoke-synchronizationcontext/

Какой-то код, раскрывающий мою идею:

   private Thread workerThread;

        private AsyncOperation operation;

        public event EventHandler SomethingHappened;

        public MySynchronizedClass()
        {
            operation = AsyncOperationManager.CreateOperation(null);

            workerThread = new Thread(new ThreadStart(DoWork));

            workerThread.Start();
        }

        private void DoWork()
        {
            operation.Post(new SendOrPostCallback(delegate(object state)
            {
                EventHandler handler = SomethingHappened;

                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }), null);

            operation.OperationCompleted();
        }

Итак, чтобы отменить поток, могу ли я снова вызвать operation.Post () с функцией делегата, которая вызвала исключение? Или это подождет, пока не завершится первая операция.

Simon 20.11.2008 11:58

... или PostOperationCompleted прервет операцию?

Simon 20.11.2008 12:19

Он не прерывается, а только завершает асинхронную операцию. Я думаю, вы можете даже опустить его. Другой способ - не использовать AsyncOperationManager, но, как говорят, он лучше: SynchronizationContext syncContext; this.syncContext = SynchronizationContext.Current; this.syncContext.Post (делегат {}, ноль)

netadictos 20.11.2008 13:35

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

Simon 20.11.2008 15:06

Я не вижу этого ясно. bw.CancelAsync () - единственный вариант, который я вижу. Другой вариант - использовать АОП. Посмотрите этот проект: secure.codeproject.com/KB/cs/AOPInvokeRequired.aspx

netadictos 20.11.2008 15:40

АОП выглядит очень интересно, но все же похоже, что он применяется для отдельных функций. Я ищу что-то, что можно применить к каждой строке функции. У меня ужасное чувство, что единственный способ сделать это - проанализировать MSIL и использовать Reflection.emit для создания нового метода.

Simon 21.11.2008 12:15

Я отвечаю вам здесь, потому что комментарии слишком короткие. Я лично использую BackgroundWorker, и когда метод завершается, поток освобождается. В основном то, что вы делаете:

bw = new BackgroundWorkerExtended();
bw.DoWork += (DoWorkEventHandler)work;
bw.WorkerSupportsCancellation = true;
//bw.WorkerReportsProgress = true;
bw.RunWorkerCompleted += (RunWorkerCompletedEventHandler)workCompleted;
//bw.ProgressChanged+=new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerAsync();

Другое дело, если вы используете Post, он будет асинхронным, если вы используете Send, он будет синхронным.

Отменить: bw.CancelAsync ();

Это хорошо, но мне все равно нужно вызывать Invoke (), если я обновляю какие-либо элементы управления записью. Я действительно ищу что-то, что обернет все вызовы, которые необходимо вызвать - возможно, мне нужно взглянуть на динамические методы.

Simon 20.11.2008 15:18

В дополнение к предложению BackgroundWorker и связанному с ним комментарию BackgroundWorker выполняет маршалинг обратного вызова OnProgress обратно в поток пользовательского интерфейса, поэтому вы МОЖЕТЕ обновлять данные оттуда без необходимости выполнять Invoke ().

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

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

обратный вызов OnProgress бесполезен для меня - мне все равно придется написать 100 различных функций, по одной для каждой из 100 строк кода.

Simon 20.11.2008 15:48

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