Могу ли я создать поток, который может изменять пользовательский интерфейс и который я могу прервать? - PullRequest
2 голосов
/ 19 ноября 2008

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

Однако, чтобы безопасно изменить элементы управления в моей форме, мне нужно использовать методы 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();
}

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

Ответы [ 4 ]

3 голосов
/ 19 ноября 2008

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

Второе - старайтесь избегать прерывания потоков; это нехорошо - вы должны предпочесть чистое отключение (например, отмену, которую поддерживает 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;
        }
    }

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

0 голосов
/ 20 ноября 2008

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

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

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

0 голосов
/ 20 ноября 2008

Я отвечу вам здесь, потому что комментарии слишком короткие. Я лично использую 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 ();

0 голосов
/ 20 ноября 2008

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

Читайте об этом в: http://www.codeproject.com/KB/cpp/SyncContextTutorial.aspx http://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();
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...