0

我正在尝试构建一个进度/取消表单以在我的 WinForms 应用程序中使用,该应用程序运行任何可等待的“操作”,同时为用户提供一些进度信息和取消操作的机会。

因为表单使用 显示ShowDialog(),所以它是一个模态表单,可以很好地禁用下面的表单 - 所以我不需要乱用禁用其他表单上的所有控件。

他们实现它的方式,我完全希望你撕成碎片:-),是在Form.Load事件处理程序期间等待操作的结果,然后在操作完成后关闭表单(无论是因为它运行完成、被取消或引发异常)。

public partial class ProgressForm<T> : Form
{
    private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
    private Progress<string> _progress = new Progress<string>();
    private Func<IProgress<string>, CancellationToken, Task<T>> _operation = null;
    private Exception _exception = null;
    private T _result = default(T);


    public static T Execute(Func<IProgress<string>, CancellationToken, Task<T>> operation)
    {
        using (var progressForm = new ProgressForm<T>())
        {
            progressForm._operation = operation;

            progressForm.ShowDialog();

            if (progressForm._exception != null)
                throw progressForm._exception;
            else
                return progressForm._result;
        }
    }


    public ProgressForm()
    {
        InitializeComponent();

        this._progress.ProgressChanged += ((o, i) => this.ProgressLabel.Text = i.ToString());
    }

    private async void ProgressForm_Load(object sender, EventArgs e)
    {
        try
        {
            this._result = await this._operation(this._progress, this._cancellationTokenSource.Token);
        }
        catch (Exception ex) // Includes OperationCancelledException
        {
            this._exception = ex;
        }

        this.Close();
    }

    private void CancelXButton_Click(object sender, EventArgs e)
    {
        if (this._cancellationTokenSource != null)
            this._cancellationTokenSource.Cancel();
    }
}

这被称为:

int numberOfWidgets = ProgressForm<int>.Execute(CountWidgets);

... whereCountWidgets()是一个可以等待的东西(在这种情况下,一个函数返回Task<int>,带有适当的 IProgress 和CancellationToken参数)。

到目前为止,它工作得很好,但我想添加一个“功能”。理想情况下,我希望表单保持不可见(比如说)一秒钟,这样如果操作真的很快完成,就不会出现“闪烁”,因为表单显示然后立即再次隐藏。

所以,我的问题是如何在表格显示之前引入 1s 延迟。显然,我仍然想立即开始操作,但是一旦我“等待”操作的结果,我就不再控制(可以这么说),因为控制将返回给Form.Load事件处理程序的调用者- 这将继续显示表格的工作。

我怀疑本质上我真的需要第二个线程,并且我需要在阻塞主 UI 线程时在该线程上执行操作。(我知道阻止 UI 线程是不受欢迎的,但在这种情况下,我认为这实际上是我需要的)。

有很多不同的方法可以创建线程等,我不确定如何在新的“async/await”世界中做到这一点......

4

2 回答 2

1

如果您想延迟显示表单,我不建议您保留部分内容。我将独立调用该操作,然后创建一个TimerTick事件处理程序检查任务是否完成,如果完成,则不执行任何操作。否则它应该创建表单,将IProgress<T>andCancellationTokenSource和 task 传递到表单中。您仍然可以等待已经开始的任务。对于要启动的任务,在创建表单之前需要进度对象和取消令牌——因此需要独立创建......

于 2012-09-20T13:02:13.173 回答
1

我认为您必须将“任务运行器”与“对话框”分开才能做到这一点。首先,一个响应进度并可以发出取消的对话框:

public partial class ProgressForm : Form
{
  private readonly CancellationTokenSource _cancellationTokenSource;

  public ProgressForm(CancellationTokenSource cancellationTokenSource, IProgress<string> progress)
  {
    InitializeComponent();

    _cancellationTokenSource = cancellationTokenSource;
    progress.ProgressChanged += ((o, i) => this.ProgressLabel.Text = i.ToString());
  }

  public static void ShowDialog(CancellationTokenSource cancellationTokenSource, IProgress<string> progress)
  {
    using (var progressForm = new ProgressForm(cancellationTokenSource, progress))
    {
        progressForm.ShowDialog();
    }
  }

  private void CancelXButton_Click(object sender, EventArgs e)
  {
    if (this._cancellationTokenSource != null)
      this._cancellationTokenSource.Cancel();
  }
}

接下来,实际的“任务运行者”:

public static class FriendlyTaskRunner
{
  public static async Task<T> Execute<T>(Func<CancellationToken, IProgress<string>, Task<T>> operation)
  {
    var cancellationTokenSource = new CancellationTokenSource();
    var progress = new Progress<string>();
    var timeout = Task.Delay(1000);
    var operationTask = operation(cancellationTokenSource.Token, progress);

    // Synchronously block for either the operation to complete or a timeout;
    //  if the operation completes first, just return the result.
    var completedTask = Task.WhenAny(timeout, operationTask).Result;
    if (completedTask == operationTask)
      return await operationTask;

    // Kick off a progress form and have it close when the task completes.
    using (var progressForm = new ProgressForm(cancellationTokenSource, progress))
    {
      operationTask.ContinueWith(_ => { progressForm.Close(); });
      progressForm.ShowDialog();
    }

    return await operationTask;
  }
}

请注意,同步阻塞 UI 线程可能会导致死锁——在这种情况下,如果operation尝试同步回 UI 线程,它将被阻塞,直到超时之后——所以它不是真正的“死锁”,但效率很低。

于 2012-09-20T13:07:54.613 回答