我正在构建一个多线程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的结果。但这是在这种情况下使用它的正确方法吗?我只是担心任何资源泄漏,但在调试时,回调似乎在我的计时器开始工作之前执行。
答案 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();
}
}
}