2012-03-12 2 views
10

У меня есть небольшое приложение winforms, которое выполняет длительный процесс в другом потоке с помощью задачи TPL. Во время этого продолжительного процесса я хотел бы обновить пользовательский интерфейс (индикатор выполнения или что-то еще). Есть ли способ сделать это без необходимости .ContinueWith()?Как обновить пользовательский интерфейс из дочерних задач в WinForms

public partial class Form1 : Form 
{ 
    private Task _childTask; 

    public Form1() 
    { 
     InitializeComponent(); 

     Task.Factory.StartNew(() => 
     { 
      // Do some work 
      Thread.Sleep(1000); 

      // Update the UI 
      _childTask.Start(); 

      // Do more work 
      Thread.Sleep(1000); 
     }); 

     _childTask = new Task((antecedent) => 
     { 
      Thread.Sleep(2000); 
      textBox1.Text = "From child task"; 
     }, TaskScheduler.FromCurrentSynchronizationContext()); 


    } 
} 

Выполнение этого кода я получаю вездесущее исключение:

Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.

+0

Думаю, вам нужно вставить ссылку на текстовое поле в поток. – jwillmer

ответ

10

Да, вы можете явно вызывать BeginInvoke на Window/Control, который вы хотите общаться. В вашем случае это будет выглядеть так:

this.textBox.BeginInvoke(new Action(() => 
{ 
    this.textBox.Text = "From child task."; 
})); 
+0

Это определенно выглядит на пути, но я получаю ошибку компиляции: Невозможно преобразовать лямбда-выражение для ввода «System.Delegate», потому что это не тип делегата – devlife

+0

Я создал действие и передал в действии вместо анонимного метода. Не уверен, почему выше не работает, но он получил меня на 99%. Спасибо Дрю. – devlife

+1

@devlife Ой, это моя вина за то, что вы так привыкли работать с новыми API. Да, вам нужно явно связать с новым действием (...) из-за того, как была разработана подпись старого API WinForms BeginInvoke. Я уточню свой ответ. –

5

Вы прохождение TaskScheduler в state (или antecedent, как вы назвали его). Это не имеет большого смысла.

Я не уверен, что именно вы хотите сделать, но вам нужно указать TaskScheduler, когда вы начинаете Task, а не когда вы его создаете. Кроме того, кажется, что childTask не должно быть поле:

var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

Task childTask = null; 

Task.Factory.StartNew(
    () => 
    { 
     Thread.Sleep(1000); 
     childTask.Start(scheduler); 
     Thread.Sleep(1000); 
    }); 

childTask = new Task(
    () => 
    { 
     Thread.Sleep(2000); 
     textBox1.Text = "From child task"; 
    }); 

Конечно, все это будет намного проще с C# 5 и await:

public Form1() 
{ 
    InitializeComponent(); 

    StartAsync(); 
} 

private async void StartAsync() 
{ 
    // do some work 
    await Task.Run(() => { Thread.Sleep(1000); }); 

    // start more work 
    var moreWork = Task.Run(() => { Thread.Sleep(1000); }); 

    // update the UI, based on data from “some work” 
    textBox1.Text = "From async method"; 

    // wait until “more work” finishes 
    await moreWork; 
} 

Вы не можете make async, но вы можете запустить метод async. «Doing work» не работает в потоке пользовательского интерфейса, потому что он был запущен явно через Task.Run(). Но так как StartAsync() был вызван напрямую, он выполняется в потоке пользовательского интерфейса.

+0

Вот и все. Я не хочу ждать завершения одной задачи для выполнения другой задачи. – devlife

+0

Я обновил свой пример, чтобы лучше показать, что я пытался понять. – devlife

+0

В этом случае мой первый образец будет работать для вас. Я также обновил версию C# 5. – svick

Смежные вопросы