我有一个C#Windows应用程序,在Form1上有一个按钮,按下时会运行一个长程序。程序运行时,我希望用户界面可用,所以我将大部分代码放入一个单独的线程中。作为测试,我将代码放入一个线程中,看看它是否会有任何问题。我有2个问题。我的最终愿望是让UI工作,所以如果这不是开始新线程的最佳方式,请告诉我。
首先,虽然编译了程序,但我创建的线程没有看到主线程变量中的所有值。大多数字符串都是空的,int和float值为0.在线程中保留其值的唯一变量是使用值创建然后从未更改的变量。显然,我应该能够看到所有变量中的所有值。
其次,我在表单上有一个文本框,我附加了文本,以便我可以提供有关长时间运行程序的信息。文本框显示主线程中的信息没有问题,但我创建的线程没有显示任何内容。我希望Form1上的文本框也可以从线程更新。
我在Windows XP上使用Visual Studio 2008。
这些是变量的定义。它们位于应用程序的Program.cs部分。
partial class Form1
{
string TBI_File = "";
int junk = 27;
string junkstr = "Two out of three ain\'t bad";
double RADD;
string PROGRAMMER = "Don and Jim";
float currentSize = 8.25F;
float sizechange = 10.0F;
}
在主线程中(按下Button后)我创建了新线程。我从http://msdn.microsoft.com/en-us/library/aa645740(v=vs.71).aspx复制并修改了这段代码我对Abort和Join进行了评论,因为在测试的这一点上我希望线程包继续运行直到我单独停止它。
Wprintf("Alpha.Beta starting");
Alpha oAlpha = new Alpha();
// Create the thread object, passing in the Alpha.Beta method
// via a ThreadStart delegate. This does not start the thread.
Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));
// Start the thread
oThread.Start();
// Spin for a while waiting for the started thread to become
// alive:
while (!oThread.IsAlive) ;
// Put the Main thread to sleep for 1 millisecond to allow oThread
// to do some work:
//original
//Thread.Sleep(1);
Thread.Sleep(10);
// Request that oThread be stopped
//oThread.Abort();
// Wait until oThread finishes. Join also has overloads
// that take a millisecond interval or a TimeSpan object.
//oThread.Join();
Wprintf("Alpha.Beta has finished");
以下是该线程运行的代码。
public class Alpha : Form1
{
// This method that will be called when the thread is started
public void Beta()
{
while (true)
{
//Console.WriteLine("Alpha.Beta is running in its own thread.");
Wprintf("Alpha.Beta is running in its own thread. " +
" RADD: " + RADD +
" CurrentSize: " + currentSize.ToString() +
" TBI_File: " + TBI_File +
" PROGRAMMER: " + PROGRAMMER +
" sizechange: " + sizechange.ToString() +
" junk: " + junk +
" junkstr: " + junkstr);
textBox1.AppendText("Alpha.Beta is running in its own thread.");
}
}
};
Wprintf将该消息附加到日志文件,并将消息添加到文本框中。它适用于整个程序,除了附加到文本框的末尾不能从创建的线程起作用。我在上面添加了TextBox1.AppendText(在线程中)以尝试使其工作,但它没有做任何事情,并且在线程的文本框中没有显示任何消息。
日志文件的部分如下所示。日志文件是从线程中追加的,所以我可以看到变量的值在线程中(我还查看了调试器中的变量并得到了相同的值)变量变量是RADD和TBI_FILE,你可以在下面看到RADD是0.0,TBI_File在线程中是''。其他程序在程序中没有更改,只是获得了声明时设置的值。
Alpha.Beta is running in its own thread. RADD: 0 CurrentSize: 8.25 TBI_File: PROGRAMMER: Don and Jim sizechange: 10 junk: 27 junkstr: Two out of three ain't bad
我在这里询问了这个问题的早期版本:Initial Form of C# program unavailable while the program is running
正如我之前所说,我需要有UI(文本框并点击退出的X),所以如果这不是一个好方法,请告诉我。
谢谢,
答案 0 :(得分:5)
您想要考虑几件事。
最重要的是,您无法从后台线程修改UI。只有UI线程可以修改控件。所以你的textBox1.AppendText
不会起作用。您需要调用Invoke以与UI线程同步,如下所示:
this.Invoke((MethodInvoker) delegate
{
textBox1.Append("Alpha.Beta is running in its own thread.");
});
当然,由于您的UI线程正在等待后台线程完成,因此不会更新UI。
您可能很难管理自己的主题,但最好还是使用BackgroundWorker来处理大部分讨厌的细节。
// set up the worker
BackgroundWorker worker = new BackgroundWorker();
worker.ReportsProgress = true;
worker.DoWork = worker_DoWork; // method that's called when the worker starts
// method called to report progress
worker.ProgressChanged = worker_progressChanged;
// method called when the worker is done
worker.RunWorkerCompleted = worker_workCompleted;
// start worker
worker.RunWorkerAsync();
void worker_DoWork(object sender, DoWorkEventArgs e)
{
//Console.WriteLine("Alpha.Beta is running in its own thread.");
Wprintf("Alpha.Beta is running in its own thread. " +
" RADD: " + RADD +
" CurrentSize: " + currentSize.ToString() +
" TBI_File: " + TBI_File +
" PROGRAMMER: " + PROGRAMMER +
" sizechange: " + sizechange.ToString() +
" junk: " + junk +
" junkstr: " + junkstr);
worker.ReportProgress(0, "Alpha.Beta is running in its own thread.");
}
void worker_progressChanged(object sender, ProgressChangedEventArgs e)
{
textBox1.Append((string)e.UserState);
}
void worker_workCompleted(object sender, RunWorkerCompletedEventArgs e)
{
textBox1.Append("Worker done!");
}
你的UI线程启动了worker,然后继续。不要让它等待工人完成。如果您希望在工作完成时收到通知,则可以处理RunWorkerCompleted
事件。或者,如果您只想轮询以查看工作程序是否已完成,则可以定时检查IsBusy
属性。不过,您最好使用RunWorkerCompleted
。
不要永远让你的UI线程等待后台线程完成。如果你这样做,那么拥有后台线程的重点是什么?
答案 1 :(得分:2)
您可以在Form的实例中设置值。然后,您创建一个继承自Form的Alpha实例,从而执行Form所做的初始化。在Form中进行的其他更改将不会在其他实例中显示(不包括静态变量)。您应该更新Alpha实例或使用相同的实例。
除了主要线程之外,您无法从任何线程访问控件。正确的方法是使用Control.Invoke,例如参见Thread Control.Invoke
此外,等待你的方式(一段时间)将使主线程卡住。如果你想等待一些事情要完成 - 你将不得不处理事件(让工作线程信号完成,或者你是一个背景工作者并注册work_completed事件)。
其他 - 您确定要继承Form吗?这真的有必要吗?
答案 2 :(得分:1)
首先,让我就这种情况给你一些个人建议: 1)在这种情况下,我宁愿使用BackgroundWorker而不是原始的Thread类。 2)线程(无论哪种类)都不能直接与UI或其他线程通信,或者至少不应该。(/ p>
- 让我们回答: 由于Threads不能/不应该直接访问主线程变量,因此必须将它们作为参数传递给BackgroundWorker。
以下是一个不言自明的代码,如果您还有任何问题,请在下方发表评论。
private BackgroundWorker worker = new BackgroundWorker();
public Form1()
{
InitializeComponent();
//Register the event/handlers for: Do Work, ProgressChanged, and Worker Completed.
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.WorkerReportsProgress = true; //Let's tell the worker that it WILL be ABLE to report progress.
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); //Method that will be called when the progress has been changed.
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); //Method that will be called when the thread finish executing.
//Start the thread async.
worker.RunWorkerAsync();
}
/// <summary>
/// Method that will run in a new thread async from the main thread.
/// </summary>
/// <param name="e">Arguments that are passed to the Worker Thread (a file, path, or whatever)</param>
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
//Get the argument. In this example I'm passing a pathFile.
string pathFile = (string)e.Argument;
for (int i = 0; i <= 100; i+=10) //For demonstration purposes we're running from 0 to 99;
{
System.Threading.Thread.Sleep(100); //Sleep for demonstration purposes.
//I want to update the Log, so the user will be notified everytime
//the log is updated through the ReportProgress event.
string myLog = i + " ";
//Invoke the event to report progress, passing as parameter the
//percentage (i) and the current log the thread has modified.
worker.ReportProgress(i, myLog);
}
e.Result = "I've made it!!!!! - My complex cientific calculation from NASA is 654.123.Kamehameha)";
}
/// <summary>
/// Invoked when the worker calls the ReportProgress method.
/// </summary>
/// <param name="e">The arguments that were passed throgh the ReportProgress method</param>
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Get the Percentage and update the progressbar.
progressBar1.Value = e.ProgressPercentage;
//Get the EventArgs and respectively the new log that the thread has modified and append it to the textbox.
textBox1.AppendText((string)e.UserState);
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Lets check whether the worker has runned successfully (without cancelling and without any errors)
if (!e.Cancelled && e.Error == null)
{
//Lets display the result (Result is an object, so it can return an entire class or any type of data)
MessageBox.Show((string)e.Result);
}
}