2

我想在后台线程上执行一些长时间运行的操作(例如监听操作系统引发的一些事件)。大多数情况下,操作会连续运行而不会出现任何问题。但是在某些罕见的情况下,操作系统级别的 API 会发送一些错误代码,我需要从后台线程引发异常,该异常必须传播到主线程以将其显示给我的WinFrom应用程序的用户。

我决定用BackgroundWorker这个。但是根据 TPL 上的各种博客,.NET 4.0 提供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'sTask用于主线程不应该等到后台任务完成但同时在后台任务引发异常时获取异常详细信息的情况?

在上面的代码中,解决方案之一可能是在稍后阶段移动任务创建代码。但在这种情况下,我的问题更具学术性。

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. 最后,Form1 的代码包含一个退出按钮,其功能是在单击时终止程序。

    公共部分类Form1:Form {

        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

从表单本身开始您的长期运行操作,而不是在创建表单之前。请记住,这会Application.Run()在当前线程上启动一个消息循环,但这意味着您可以使用该消息循环从Timer类轮询您的任务。

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.5async和的奢侈品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 回答