更新状态栏标签异步/等待

时间:2019-10-03 01:38:55

标签: c# winforms user-interface async-await task

我有一个带有工具StripStatusLabel的WinForm。有一个按钮,它产生一个新线程来执行其任务。在此任务期间和完成后,状态标签需要更新。 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 个答案:

答案 0 :(得分:1)

如果Export方法中确实有一些等待的内容,那么我认为将其设为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 :(得分:1)

报告进度的标准方法是使用IProgress<T>界面。您已经可以使用(Progress<T>)此接口的实现,并且该接口的实现是通用的,因此您可以提供所需的任何类型的参数。在下面的示例中,参数为string。关键是事件Progress.ProgressChanged正在UI线程中运行,因此您不必担心。

// 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);
    }
}

答案 2 :(得分:0)

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