Обновление метки строки состояния async / await - PullRequest
1 голос
/ 03 октября 2019

У меня есть WinForm с toolStripStatusLabel. Есть кнопка, которая порождает новый поток для выполнения своей задачи. Метка состояния должна обновляться во время и после выполнения этой задачи. Элементы GUI находятся в основном потоке. Если я хочу добиться этого, могу ли я разместить соответствующие строки, чтобы обновить метку, где комментарии находятся ниже в фрагменте кода ниже? Кроме того, мне нужно открыть другую форму при нажатии на этот ярлык. Насколько я понимаю, асинхронное кодирование должно быть простым, с использованием обработчика события для метки и того факта, что управление вернется к вызывающей стороне асинхронного метода. Это верно? Я относительно новичок в многопоточном и асинхронном программировании, поэтому я совершенно сбит с толку.

// this is running in the main GUI thread
private async void Export_execute_Click(object sender, System.EventArgs args)
{
    try
    {
        await System.Threading.Tasks.Task.Run(() => do_export(filename, classes, System.TimeZoneInfo.ConvertTimeToUtc(timestamp)));
        // if this is successful, status label should be update (task successful)
    }
    catch (System.Exception e)
    {
        // status label should be updated (task failed)
    }
}

Ответы [ 3 ]

1 голос
/ 03 октября 2019

Если в методе экспорта есть что-то буквально ожидаемое, то, я думаю, лучше сделать метод async.

private async void Export_execute_Click(object sender, EventArgs e)
{
    try
    {
        await ExportAsync("file1", "classA", DateTime.Now);
        toolStripStatusLabel.Text = $"Export finished at {DateTime.Now}";
    }
    catch (Exception ex)
    {
        toolStripStatusLabel.Text = $"Export failed, {ex.ToString()}";
    }
}

private async Task ExportAsync(string fileName, string classes, DateTime timestamp)
{
    toolStripStatusLabel.Text = $"Export start at {timestamp}";
    await Task.Delay(TimeSpan.FromSeconds(5));
    toolStripStatusLabel.Text = $"Have first half done {timestamp}";
    await Task.Delay(TimeSpan.FromSeconds(5));
}

private void toolStripStatusLabel_Click(object sender, EventArgs e)
{
    Form2 frm2 = new Form2();
    frm2.Show();
}
1 голос
/ 03 октября 2019

Стандартный способ сообщить о прогрессе - использовать интерфейс IProgress<T>. Уже есть реализация этого интерфейса, которую вы можете использовать (Progress<T>), и она является общей, так что вы можете предоставить любой тип аргумента, который вы хотите. В приведенном ниже примере аргументом является string. Ключевым моментом является то, что в потоке пользовательского интерфейса выполняется событие Progress.ProgressChanged, поэтому вам не нужно об этом беспокоиться.

// This will run in the UI thread
private async void Export_Execute_Click(object sender, EventArgs args)
{
    try
    {
        var progress = new Progress<string>();
        progress.ProgressChanged += ExportProgress_ProgressChanged;
        // Task.Factory.StartNew allows to set advanced options
        await Task.Factory.StartNew(() => Do_Export(filename, classes,
            TimeZoneInfo.ConvertTimeToUtc(timestamp), progress),
            CancellationToken.None, TaskCreationOptions.LongRunning,
            TaskScheduler.Default);
        toolStripStatusLabel.Text = $"Export completed successfully";
    }
    catch (Exception e)
    {
        toolStripStatusLabel.Text = $"Export failed: {e.Message}";
    }
}

// This will run in the UI thread
private void ExportProgress_ProgressChanged(object sender, string e)
{
    toolStripStatusLabel.Text = e;
}

// This will run in a dedicated background thread
private void Do_Export(string filename, string classes, DateTime timestamp,
    IProgress<string> progress)
{
    for (int i = 0; i < 100; i += 10)
    {
        progress?.Report($"Export {i}% percent done");
        Thread.Sleep(1000);
    }
}
0 голосов
/ 03 октября 2019

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

Обратите внимание, что Export_execute_Click больше не помечается как async в этом сценарии.

Пример:

private void Export_execute_Click(object sender, System.EventArgs args) {

    // Method level objects are accessible throughout this process
    bool error = false;

    // Process
    BackgroundWorker worker = new BackgroundWorker {
        WorkerReportsProgress = true
    };

    // This executes on main thread when a progress is reported
    worker.ProgressChanged += (e, ea) => {
        if (ea.UserState != null) {
            // ea.UserState.ToString() contains the string progress message
        }
    };

    // This executes as an async method on a background thread
    worker.DoWork += (o, ea) => {
        try {
            var response = do_export(filename, classes, System.TimeZoneInfo.ConvertTimeToUtc(timestamp)));
            if (response == whatever) {
                worker.ReportProgress(0, "Response from do_export() was `whatever`");
            } else {
                worker.ReportProgress(0, "Response from do_export() was something bad");
                error = true;
            }

        } catch (System.Exception e) {
            worker.ReportProgress(0, $"do_export() failed: {e}");
        }
    };

    // This executes on the main thread once the background worker has finished
    worker.RunWorkerCompleted += async (o, ea) => {
        // You can communicate with your UI normally again here
        if (error) {
            // You had an error -- the exception in DoWork() fired
        } else {
            // You're all set
        }

        // If you have a busy-indicator, here is the place to disable it
        // ...
    };

    // I like to set a busy-indicator here, some sort of ajax-spinner type overlay in the main UI, indicating that the process is happening
    // ...

    // This executes the background worker, as outlined above
    worker.RunWorkerAsync();
}
...