开发者

WPF & Multi-threading questions

开发者 https://www.devze.com 2023-02-18 13:49 出处:网络
I\'m working on building a multi-threaded UI. I would like long processes to be handled by the BackgroundWorker class, and have a small timer on the UI to keep track of how long the process is taking.

I'm working on building a multi-threaded UI. I would like long processes to be handled by the BackgroundWorker class, and have a small timer on the UI to keep track of how long the process is taking. It's my first time building such a UI, so I'm reading up on related resources on the web. My test code is thus:

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

The idea is when the button is clicked, we take the status text from a Label (e.g. "Processing...") then append the ti开发者_开发知识库me onto it every second. I could not access the UI elements from the Timer class as it's on a different thread, so I had to use delegates to get and set the text.

It works, but is there a better way to handle this? The code seems much for such a basic operation. I'm also not fully understanding the EndInvoke bit at the bottom. I obtained the snippet of code from this thread Should One Always Call EndInvoke a Delegate inside AsyncCallback?

I understand the idea of EndInvoke is to receive the result of BeginInvoke. But is this the correct way to use it in this situation? I'm simply worried about any resource leaks but when debugging the callback appears to execute before my timer starts working.


Don't use a separate timer to read the progress of your BackgroundWorker and update the UI. Instead, make the BackgroundWorker itself "publish" its progress to the UI directly or indirectly.

This can be done pretty much anyway you want to, but there's a built-in provision exactly for this case: the BackgroundWorker.ProgressChanged event.

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

}

This way you can simply attach your own handler to ProgressChanged and update the UI using BeginInvoke from there. The timer and everything related to it can (and should) go.


You can use timer to update UI. It is normal practice. Just instead of System.Timer.Timer I suggest use System.Windows.Threading.DispatcherTimer. The DispatcherTimer runs on the same thread as the Dispatcher. Also, instead of BackgroundWorker you can use ThreadPool. Here is my sample:

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


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

        }
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号