2

バックグラウンドスレッドで長時間実行される操作(OSによって発生するイベントのリッスンなど)を実行したい。ほとんどの場合、動作は問題なく継続して実行されます。しかし、特定のまれな状況では、OSレベルのAPIがエラーコードを送信し、アプリケーションのユーザーに表示するためにメインスレッドに伝播する必要があるバックグラウンドスレッドから例外を発生させる必要がありWinFromます。

これに使うことにしBackgroundWorkerました。しかし、.NET 4.0は、TPLのさまざまなブログによると、より優れたオプションであるTaskクラスを提供します。Task Parallel Library

私のアプリケーションでは、実際のフォームが表示される前に、バックグラウンドタスクを開始する必要があります。実際のコードは非常に複雑なので、リアルタイムの問題をシミュレートするサンプルコードをいくつか作成しました。

public static Task task;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            ThreadTest tt = new ThreadTest();
            task = new Task(() => tt.PerformTask("hi"));
            task.Start();
            try
            {
                task.Wait();
            }
            catch (AggregateException aggregateException)
            {
                // Handle exception here.
            }

            Application.Run(new Form1());
        }

このコードでは、バックグラウンドタスクが例外なく実行され続け、task.Wait()呼び出しによって現在のスレッドがバックグラウンドタスクが終了するまで待機するという理由だけで、メインフォームが表示されることはありません。

メインスレッドがバックグラウンドタスクが終了するまで待機するべきではないが、同時に、バックグラウンドタスクから例外が発生するたびに例外の詳細を取得する必要があるようなシナリオにTPLを使用できますか?Task

上記のコードでは、解決策の1つは、後の段階でタスク作成コードを移動することです。しかし、この場合、私の質問はより学術的です。

4

2 に答える 2

3

はい、できます。以下のコードをご覧ください。

  1. プログラムコードは次のとおりです。

         /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
    
        CancellationTokenSource  cancellationTokenSource = new CancellationTokenSource();
    
        Task  longRunningTask = new Task((state) =>
            {
                LongRunningWork.DoWork(  cancellationTokenSource.Token);
    
            },cancellationTokenSource.Token,TaskCreationOptions.LongRunning);
    
        var newForm = new Form1(cancellationTokenSource); 
        new Thread((state) =>
            {
                longRunningTask.Start();
    
                try
                {
                    longRunningTask.Wait();
                }
                catch (AggregateException exception)
                {
                    Action<Exception> showError = (ex) => MessageBox.Show(state as Form, ex.Message);
    
                    var mainForm = state as Form;
                    if (mainForm != null)
                    {
                        mainForm.BeginInvoke(showError, exception.InnerException);
                    }
    
                }
            }).Start(newForm);
        Application.Run(newForm);
    
  2. そして、長時間実行されるタスクのコードは次のとおりです。

    public class LongRunningWork
    {
        public static void DoWork( CancellationToken cancellationToken)
        {
    
            int iterationCount = 0;
            //While the 
            while (!cancellationToken.IsCancellationRequested &&iterationCount <5)
            {
                //Mimic that we do some long jobs here
                Thread.Sleep(1000);
    
                iterationCount++;
                //The jobs may throw the exception on the specific condition
                if (iterationCount ==5)
                {
                    throw  new InvalidOperationException("Invalid action");
                }
    
    
            }
    
            //cancel the task 
            cancellationToken.ThrowIfCancellationRequested();
        }
    }
    
  3. 最後に、フォーム1のコードには、クリックするとプログラムを終了する機能を持つ終了ボタンが含まれています。

    パブリック部分クラスForm1:フォーム{

        private CancellationTokenSource _cancellationTokenSource;
    
        public Form1()
        {
            InitializeComponent();
        }
    
        public Form1(CancellationTokenSource cancellationTokenSource):this()
        {
            _cancellationTokenSource = cancellationTokenSource;
        }
    
        private void exitBtn_Click(object sender, EventArgs e)
        {
            //Cancel out the task
            if (_cancellationTokenSource != null)
            {
                _cancellationTokenSource.Cancel();
            }
    
            //Exit the program
            Application.Exit();
    
        }
    }
    
于 2012-12-26T10:18:51.007 に答える
1

フォームが作成される前ではなく、フォーム自体から長時間実行する操作を開始します。これにより、現在のスレッドでメッセージループが開始されますが、これは、そのメッセージループを使用して、 TimerクラスApplication.Run()からタスクをポーリングできることを意味します。

class Form1 : Form
{
    private Timer PollingTimer;
    private Task BackgroundTask;

    public Form1()
    {
        InitializeComponent();

        // Begin the background task.
        ThreadTest tt = new ThreadTest();
        this.BackgroundTask = new Task(() => tt.PerformTask("hi"));
        this.BackgroundTask.Start();

        // Monitor the task's status by polling it regularly.
        this.PollingTimer = new Timer();
        this.PollingTimer.Interval = 1000;        // In milliseconds.
        this.PollingTimer.Tick += timerCallback;
        this.PollingTimer.Start();
    }        

    private timerCallback(object sender, EventArgs e)
    {
        if (this.BackgroundTask.IsFaulted)
        {
            // Exception information is in BackgroundTask.Exception.
        }
    }
}

ポーリング(私が行う)が嫌いな場合は、タスクから例外をキャッチし、それをUIスレッドにマーシャリングする必要があります。これを行うための最良の方法は、タスク自体で例外をキャッチせず、エラー時にのみ実行される継続メソッドを提供することです。

class Form1 : Form
{
    private Task BackgroundTask;

    public Form1()
    {
        InitializeComponent();

        // Capture the UI thread context.
        // (Note, it may be safer to run this in the Form.Load event than the constructor.
        var uiContext = TaskScheduler.FromCurrentSynchronizationContext();

        // Begin the background task.
        ThreadTest tt = new ThreadTest();
        this.BackgroundTask = new Task(() => tt.PerformTask("hi"))
            // Schedule a continuation to be executed after the task is completed.
            .ContinueWith((t,arg) => 
            {
                // Exception information is in t.Exception
            },null, null, 
            // Only execute the continuation if the task throws an exception.
            TaskContinuationOptions.OnlyOnFaulted,
            // Execute the continuation on the UI thread we captured above. 
            uiContext);
        this.BackgroundTask.Start();
    }        
}

Task.ContinueWith()およびのMSDNリファレンスTaskScheduler.FromCurrentSynchronizationContext()

そして、あなたが.NET 4.5の贅沢を持っているなら、asyncそしてawait

class Form1 : Form
{
    private Task BackgroundTask;

    public Form1()
    {
        InitializeComponent();
    }        

    private async void Form1_Load(object sender, EventArgs e)
    {
        ThreadTest tt = new ThreadTest();
        try
        {
            // Move your Task creation and start logic into a method.
            await tt.RunAsync();
        } 
        catch (Exception ex)
        {
            // Really smart compiler writers make sure you're on the right thread 
            // and everything Just Works(tm).
        }
    }
}
于 2012-12-27T10:54:28.360 に答える