WPF&多线程问题

时间:2011-03-21 12:47:05

标签: wpf multithreading

我正在构建一个多线程UI。我希望BackgroundWorker类可以处理很长的进程,并在UI上有一个小计时器来跟踪进程的持续时间。这是我第一次构建这样的UI,所以我正在阅读网络上的相关资源。因此我的测试代码是:

    private BackgroundWorker worker;
     private Stopwatch swatch = new Stopwatch();
     private delegate void simpleDelegate();
     System.Timers.Timer timer = new System.Timers.Timer(1000);
     string lblHelpPrevText = "";

     private void btnStart_Click(object sender, RoutedEventArgs e)
     {
         try
         {
             worker = new BackgroundWorker(); //Create new background worker thread
             worker.DoWork += new DoWorkEventHandler(BG_test1);
             worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BG_test1end);
             worker.RunWorkerAsync();

             simpleDelegate del = new simpleDelegate(clockTicker);
             AsyncCallback callBack = new AsyncCallback(clockEnd);
             IAsyncResult ar = del.BeginInvoke(callBack, null);                                     

             lblHelpText.Text = "Processing...";
          }
          finally
          {
             worker.Dispose(); //clear resources
          }
    }

    private void clockTicker()
            {    
                //Grab Text
                simpleDelegate delLblHelpText = delegate()
                { lblHelpPrevText = this.lblHelpText.Text; };
                this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delLblHelpText);

                //Start clock
                timer.Elapsed += new ElapsedEventHandler(clockTick);
                timer.Enabled = true;
                swatch.Start();
            }

            private void clockTick(object sender, ElapsedEventArgs e)
            {
                simpleDelegate delUpdateHelpTxt = delegate()
                { this.lblHelpText.Text = String.Format("({0:00}:{1:00}) {2}", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds, lblHelpPrevText); };
                this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delUpdateHelpTxt);
            }

     private void BG_test1(object sender, DoWorkEventArgs e)
            {
                //this.lblHelpText.Text = "Processing for 10 seconds...";
                Thread.Sleep(15000);
            }

            private void BG_test1end(object sender, RunWorkerCompletedEventArgs e)
            {
                this.lblHelpText.Text = "Process done.";
                this.timer.Enabled = false;
                this.swatch.Stop();
                this.swatch.Reset();            
            }

static void clockEnd(IAsyncResult ar)
        {
            simpleDelegate X = (simpleDelegate)((AsyncResult)ar).AsyncDelegate;
            X.EndInvoke(ar);
        }

这个想法是当点击按钮时,我们从Label中获取状态文本(例如“Processing ...”),然后每秒将时间附加到它上面。我无法访问Timer类中的UI元素,因为它位于不同的线程上,所以我不得不使用委托来获取和设置文本。

它有效,但有更好的方法来处理这个问题吗?代码似乎很适合这种基本操作。我也没有完全理解底部的EndInvoke位。我从这个帖子Should One Always Call EndInvoke a Delegate inside AsyncCallback?

中获取了代码片段

我理解EndInvoke的想法是接收BeginInvoke的结果。但这是在这种情况下使用它的正确方法吗?我只是担心任何资源泄漏,但在调试时,回调似乎在我的计时器开始工作之前执行。

3 个答案:

答案 0 :(得分:2)

请勿使用单独的计时器来阅读BackgroundWorker的进度并更新用户界面。相反,让BackgroundWorker本身“直接或间接地”将其进度“发布”到UI。

这可以在你想要的任何地方完成,但是有一个完全适用于这种情况的内置规定:BackgroundWorker.ProgressChanged事件。

private void BG_test1(object sender, DoWorkEventArgs e)
{
    for(var i = 0; i < 15; ++i) {
        Thread.Sleep(1000);
        // you will need to get a ref to `worker`
        // simplest would be to make it a field in your class
        worker.ReportProgress(100 / 15 * (i + 1));
    }

}

通过这种方式,您只需将自己的处理程序附加到ProgressChanged,然后使用BeginInvoke更新UI。计时器和与之相关的一切都可以(而且应该)去。

答案 1 :(得分:0)

您可以使用计时器更新UI。这是正常的做法。只是代替System.Timer.Timer我建议使用System.Windows.Threading.DispatcherTimer。 DispatcherTimer与Dispatcher在同一个线程上运行。此外,您可以使用ThreadPool代替BackgroundWorker。 这是我的样本:

object syncObj = new object();
Stopwatch swatch = new Stopwatch();
DispatcherTimer updateTimer; // Assume timer was initialized in constructor.

void btnStart_Click(object sender, RoutedEventArgs e) {

  lock (syncObj) {
    ThreadPool.QueueUserWorkItem(MyAsyncRoutine);
    swatch.Start();
    updateTimer.Start();
  }
}

void updateTimer_Tick(object sender, EventArgs e) {

  // We can access UI elements from this place.
  lblHelpText.Text = String.Format("({0:00}:{1:00}) Processing...", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds);
}

void MyAsyncRoutine(object state) {

  Thread.Sleep(5000);

  lock (syncObj)
    Dispatcher.Invoke(new Action(() => {
      swatch.Stop();
      updateTimer.Stop();
      lblHelpText.Text = "Process done.";
    }), null);
}

答案 2 :(得分:0)

 private void button1_Click(object sender, EventArgs e)
        {

            string strFullFilePath = @"D:\Print.pdf";


            ProcessStartInfo ps = new ProcessStartInfo();
            ps.UseShellExecute = true;
            ps.Verb = "print";
            ps.CreateNoWindow = true;
            ps.WindowStyle = ProcessWindowStyle.Hidden;
            ps.FileName = strFullFilePath;
            Process.Start(ps);
            Process proc = Process.Start(ps);
            KillthisProcess("AcroRd32");


        }
        public void KillthisProcess(string name)
        {

            foreach (Process prntProcess in Process.GetProcesses())
            {

                if (prntProcess.ProcessName.StartsWith(name))
                {
                    prntProcess.WaitForExit(10000);
                    prntProcess.Kill();
                }
            }

        }