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

...待機可能なCountWidgets()ものはどこにありますか(この場合、Task<int>適切なIProgressとCancellationTokenパラメーターを使用して関数が返されます)。

これまでのところかなりうまく機能していますが、追加したい「機能」が1つあります。理想的には、フォームを(たとえば)1秒間非表示にして、操作が非常に迅速に完了した場合に、フォームが表示されてすぐに再び非表示になる「ちらつき」がないようにします。

だから、私の質問は、フォームが表示される前に1秒の遅延を導入する方法です。もちろん、すぐに操作を開始したいのですが、操作の結果を「待つ」とすぐに、Form.Loadイベントハンドラーの呼び出し元に制御が戻されるため、(いわば)制御できなくなります。 -フォームを表示する作業を続行します。

基本的に2番目のスレッドが本当に必要であり、メインUIスレッドをブロックしている間にそのスレッドで実行する操作が必要だと思います。(UIスレッドをブロックすることは嫌われていることは知っていますが、この場合、実際に必要なものだと思います)。

スレッドなどを作成する方法はたくさんあるので、新しい「非同期/待機」の世界でこれを行う方法がわかりません...

4

2 に答える 2

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スレッドを同期的にブロックすると、デッドロックが発生する可能性があることに注意してください。この場合、operationUIスレッドに同期しようとすると、タイムアウトが発生するまでブロックされます。したがって、これは真の「デッドロック」ではありませんが、非常に非効率的です。

于 2012-09-20T13:07:54.613 に答える
1

フォームの表示を遅らせたい場合は、持っているものの一部を保持することはお勧めしません。独立して操作を呼び出してから、TimerそのTickイベントハンドラーを作成して、タスクが完了したかどうかを確認し、完了した場合は何もしません。IProgress<T>それ以外の場合は、フォームを作成し、 andCancellationTokenSourceとtaskをフォームに渡します。すでに開始されているタスクを引き続き待つことができます。タスクを開始するには、フォームを作成する前に進行状況オブジェクトとキャンセルトークンが必要になるため、個別に作成する必要があります...

于 2012-09-20T13:02:13.173 に答える