2011-02-12 41 views
0

我有一个XAML应用程序,用作自动化的UI。整个自动化可能需要20-30小时的时间才能完全执行,因此我创建了一个基本上包装线程方法(启动/停止/重置)的任务类对象。WPF/XAML:如何执行线程进程并防止主UI忙/冻结?

但是,当我在Task对象下运行自动化方法时,XAML UI很忙,我无法与其他控件交互,包括切换Thread.Set()标志的Pause按钮。

还有另一篇文章 Prevent UI from freezing without additional threads

,其中有人推荐BackgroundWorker的类此MSDN文章提到这是一个坏主意,用这个,如果它操纵的UI,这矿的确显示状态,计数的目的,对象时: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

解决这个任何想法?

private void OnButtonStartAutomationClick(object sender, RoutedEventArgs e) 
    { 
     btnPauseAutomation.IsEnabled = true; 
     Automation.Task AutomationThread = new Automation.Task(RunFullAutomation); 
    } 

    private void RunFullAutomation() 
    { 
     // do stuff that can take 20+ hours 
     // threaded so I can utilize a pause button (block) 
    } 

class Task 
{ 
    private ManualResetEvent _shutdownFlag = new ManualResetEvent(false); 
    private ManualResetEvent _pauseFlag = new ManualResetEvent(true); 
    private Thread _thread; 
    private readonly Action _action; 

    public Task(Action action) 
    { 
     _action = action; 
    } 

    public void Start() 
    { 
     ThreadStart ts = new ThreadStart(DoDelegatedMethod); 
     _thread = new Thread(ts);    
     _thread.Start(); 
     _thread.Priority = ThreadPriority.Lowest; 
    } 

    public void Resume() 
    { 
     _pauseFlag.Set(); 
    } 

    public void Stop() 
    { 
     _shutdownFlag.Set(); 
     _pauseFlag.Set(); 
     _thread.Join(); 
    } 

    private void DoDelegatedMethod() 
    { 
     do 
     { 
      _action(); 
     } 
     while (!_shutdownFlag.WaitOne(0)); 
    } 
} 
+0

请注意 - 我建议不要创建一个名为Task的类,因为它与框架的Task类冲突:http://msdn.microsoft.com/en-us/library/system.threading。 tasks.task.aspx – 2011-02-12 02:37:19

回答

3

,其中有人推荐BackgroundWorker的类此MSDN文章提到这是一个坏主意,用这个,如果它操纵的UI,这矿的确显示状态的目的,对象,当计数

BackgroundWorker实际上是理想的选择,因为它是为这种类型的场景设计的。警告的是,您不应该更改DoWork中的UI元素,而应该通过ReportProgressProgressChanged事件更改UI元素。

警告之所以存在,是“DoWork的”是在后台线程上执行。如果你从那里设置一个UI元素值,你会得到一个交叉线程异常。但是,ReportProgress/ProgressChanged会自动将呼叫编组为适合您的SynchronizationContext

1

查看WPF中的Dispatcher对象。您可以并且应该在您的场景中,在后台线程上运行长时间运行的任务,并且BackgroundWorker是一种很好的方法。当您需要更新UI时,您需要验证对UI线程的访问权限,如果您没有,则使用调度程序在UI线程上调用更新方法。

1

这里有两种可能的原因:第一,阻塞任务阻塞了UI线程而不是在后台线程上运行;第二,后台线程正在匮乏UI线程,以至于它永远没有机会回应输入。你需要找出哪些是这种情况。一个简单的方法是在你的Click处理程序中,Debug.WriteLine当前的线程ID(Thread.CurrentThread.ManagedThreadId),并在RunFullAutomation回调中执行相同的操作。

如果这些打印相同的号码,那么你有第一个问题。里德和TheZenker为此提供了解决方案。

如果这些打印不同的号码,那么你已经在工作线程,和你有第二个问题。 (BackgroundWorker可能会更优雅地让你进入工作线程,并会帮助更新UI,但它不会停止饥饿。)在这种情况下,最简单的修复可能是在启动工作线程之前设置_thread.Priority = ThreadPriority.BelowNormal;

顺便说一句,你的代码永远不会出现实际调用AutomationThread.Start,这意味着RunFullAutomation回调甚至没有执行。这只是一个错字吗?

+0

是的,这是一个错字。感谢您指出了这一点! – Wibble 2011-02-17 17:55:34

1

因为.NET 4完全支持在后台使用任务并行库异步运行任务,所以建议不要推出自己的Task类 也就是说,您可以执行Reed建议的操作,并使用BackgroundWorker是理想的,或者如果你在任务SI如何执行的性质更喜欢更多的控制,你可以从System.Threading.Tasks使用Task类并实现类似这样:

public partial class MainWindow : Window 
{ 
    CancellationTokenSource source = new CancellationTokenSource(); 
    SynchronizationContext context = SynchronizationContext.Current; 
    Task task; 
    public MainWindow() 
    { 
     InitializeComponent(); 
    } 

    private void DoWork() 
    { 
     for (int i = 0; i <= 100; i++) 
     { 
      Thread.Sleep(500); //simulate long running task 
      if (source.IsCancellationRequested) 
      { 
       context.Send((_) => labelPrg.Content = "Cancelled!!!", null); 
       break; 
      } 
      context.Send((_) => labelPrg.Content = prg.Value = prg.Value + 1, null); 
     } 
    } 

    private void Start_Click(object sender, RoutedEventArgs e) 
    { 
     task = Task.Factory.StartNew(DoWork, source.Token); 
    } 

    private void Cancel_Click(object sender, RoutedEventArgs e) 
    { 
     source.Cancel(); 
    }  
} 

DoWork()您使用WPF的SynchronizationContext并发布消息以更新您需要的用户界面。

该示例具有一个进度条和一个标签控件,该标签控件在for循环的每次迭代中都会更新。使用CancellationTokenSource来支持取消,每次迭代都会检查它。

希望这会有所帮助。