Как обновить пользовательский интерфейс из дочерних задач в WinForms - PullRequest
11 голосов
/ 12 марта 2012

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

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());


    }
}

При выполнении этого кода я получаю повсеместное исключение:

Операция с несколькими потоками недопустима:Элемент управления textBox1 доступен из потока, отличного от потока, в котором он был создан.

Ответы [ 2 ]

14 голосов
/ 12 марта 2012

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

this.textBox.BeginInvoke(new Action(() =>
{
   this.textBox.Text = "From child task.";
}));
5 голосов
/ 12 марта 2012

Вы передаете 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;
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...