1

我想在后台线程上执行一些长时间运行的操作(例如监听OS引发的某些事件)。大多数情况下,操作将持续运行,没有任何问题。但在某些罕见的情况下,操作系统级别的API会发送一些错误代码,并且我需要从后台线程中引发异常,并将其传播到主线程以显示给我的WinFrom应用程序的用户。如何获得从长时间运行的后台引发的异常没有主线程等待的任务

我决定使用BackgroundWorker这个。但是.NET 4.0提供Task类的Task Parallel Library这是TPL上各种博客的更好选择。

在我的应用程序中,我必须在显示实际窗体之前启动后台任务。由于实际的代码是相当复杂的,我已经写了一些示例代码模拟真实时间的问题:

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()调用使当前线程的主要形式等待后台任务完成!

我可以使用TPLTask这样的场景中主线程不应该等到后台任务已完成,但在同一时间,它应该得到异常的详细信息异常时从后台任务提出的?

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

回答

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类:表格{

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

这确实是非常有帮助的。谢谢你。 但是,在您的代码中,异常不会传播到“主”线程。它传播到启动任务的线程。如果我们需要显示模态MessageBox,那么主模板必须作为模态MessageBox的所有者传递,这会产生问题。不是吗? – Learner

+0

查看我编辑的程序代码版本 –

+0

太棒了!为我工作。 :) – Learner

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

MSDN参考Task.ContinueWith()TaskScheduler.FromCurrentSynchronizationContext()

而且,如果你有.NET 4.5与asyncawait奢侈品:

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). 
     } 
    } 
} 
相关问题