您的位置:首页 > 编程语言 > C#

C#与MFC的简单线程

2010-04-14 15:27 183 查看
.NET线程简述

线程是应用程序中独立的指令流,任何一个应用程序都会有一个入口点main() 方法(函数)。程序将从main() 方法的第一条语句逐条往下执行,直到执行完该方法中的所有语句。一个应用程序通常都允许有多个线程工作,而这个执行 main() 方法的线程在任何情况下都将被视为主线程。一个应用程序的进程,必须至少包含一个线程,线程是运行程序所必须的。
在很多情况下,我们希望程序同时执行多个任务。这时候就需要创建用于执行各个任务的子线程。比如 Microsoft Word 的拼写检查,当我们在 Word 中输入数据时,后台线程会对输入的数据进行相应的检查。
操作系统会调度线程。线程有一个优先级,正在处理的程序的位置计数器,一个存储其本地变量的堆栈。每一个线程都有自己的堆栈,但程序的内存和堆由同一个进程的所有线程共享。这就使得同一个进程的所有线程可以自由通信。

创建线程:创建线程有两种方式,一种是通过异步委托,这种方法我在以前的文章中有讲过,这里就不在多说。另一种是使用 System.Threading.Thread 类。
使用 Thread 类可以创建和控制线程。Thread 类的构造函数接受 ThreadStart 和 ParameterizedThreadStart 数型的委托参数。ThreadStart 定义一个返回 void 类型的无参方法。ParameterizedThreadStart 定义一个返回 void 类型,且支持一个 object 参数的方法。在创建了 Thread 对象后,就可以使用 Start 方法启动线程了。代码如下:

using System;
using System.Text;
using System.Threading;
namespace Zhnet
{
static void Main()
{
Thread tl = new Thread(new ThreadStart(ThreadDo))
tl.Start();
}

static void ThreadDo()
{
Console.WriteLine("输出在一个子线程中!");
}
}

若要更好的控制线程,将 Thread 对象作为全属变量,或使用一个全局的对列或集合来存储需要控制的线程的对象。

前台线程与后台线程:在创建线程时,可以设置 IsBackground 属性,来指定线程是前台线程还是后台线程。前台线程与后台线程的区别在于,若主线程结束,则前台线程也会强制结束。而后台线程,则并不会随前主进程的结束而马上结束。比如关闭 Word 应用程序,其拼写检查也就不会在运行了。线程控制更详细的说明,请自行查阅 MSDN。

资源竞争与死锁:由于同一进程中的线程都共享内存及堆,所以很可能多个不同的线程都会去改同一个内存。这种情况下,如果处理不好则用出现资源竞争。即几个不同的线程同时在抢同一个内存资源。解决这一问题的方法是使用资源锁。确保一次只有一个线程访问和改变共享内存。这即是线程同步,注意线程问题经常是不定期发生的,所以一定要注意多线程的同步问题。
可用于同步的技术:
● lock 语句
● Interlocked 类
● Monitor 类
● 等待句柄
● Mutex 类
● Semaphore 类
● Event 类
其中,lock 语句、Interlocked 类、Monitor 类,用于同一进程内部的同步。 Mutex 类、Semaphore 类和 Event 类提供了多个进程中的线程同步。要具体讲太多,详情就自行查看 MSDN。
C#为多个线程同步提供了自己的关键字:lock 语句。它用于锁定或解锁共享资源。在每一个需要访问共享资源的线程,访问共享资源时都使用 lock 语句限定一下,就不会出现资源竞争了。比如我们要将多个线程的搜索结果都加入到一个 ResultList 集合中,则在各个线程中执行添加时都请这样做:
lock(ResultList)
{
ResultList.Add(result);
}
这样,当一个线程获得 ResultList 的访问权时 lock 语句将为该线程锁定 ResultList。并让其它线程,等待该线程使用完成后解锁。
死锁:当一个线程正在等待语句执行完并解锁共享资源,而共享资源在该线程中解锁前又被移交到其它线程。这样一来,当前的线程正在等待另一个线程处理完共享资源后解锁。然而,由于当前的线程并没有对共享资源解除锁定,所以,它会等待当前线程解锁。这种情形就是双方都在等待,很明显,这种等待是永无止境的等待。这就是传说中的死锁。下面我们来特意构造一种这样的情况。比如 子线程执行完数据收集后,要将一个用于存储多个线程收集结果的数据集作为源绑定到网络控件上,然绑定方法需要主线程来执行:

//-- 子线程中的代码:
lock(ResultView)
{
ResultView.Rows.Add(row);
this.Invoke(new MethodInvoker(ResultBing));
}

//-- 主线程中的 ResultBing() 方法
private void ResultBing()
{
lock(ResultView)
{
dv.DataSource = ResultView;
dv.Refresh();
}
}

跨线程访问控件的方法:在.NET中,不允许直接跨线程访问控件。网上有不少人提供设置 Control.CheckForIllegalCrossThreadCalls=false 这样的方法来开禁用控件的跨线访问限制,这种方法请不要使用,它会给你的程序带来许多的不稳定因隐患。.NET中提供一种更安全的方式来解决跨线程访问控件的问题。在任何一个 .NET 控件中都有 Invoke方法及BeginInvoke方法来将对控件的操作权封送到创建它的线程。下面来细说一下这两个方法。
Invoke(MethodInvoker method)在拥有此控件的基础窗口句柄的线程上执行指定的委托。BeginInvoke(Delegate method, params Object[] args)在创建控件的基础句柄所在线程上,用指定的参数异步执行指定委托。
例用这两个方法的任意一个皆可。Invoke 方法接受一个MethodInvoker委托,该委托在System.Windows.Forms 中定义,它接受一个返回类型为void的无参方法。但在WindowsMoble中并没有定义这个委托。BeginInvoke 接受任意委托,你可以跟据自己的需要来定义这个委托。将对控件的操作的方法绑定到委托中用这两个方法来执行,返回上面的死锁代码中this.Invoke(new MethodInvoker(ResultBing)) 就是使用 Invoke 方法来执行对控件的跨线程操作。

代码加入线程可解决界面用到循环代码运行时假死现象,下面是实际在C#和MFC中用到的简单线程举例。

C#:

using System.Threading;

#region 线程
/// <summary>
/// 线程
/// </summary>
private void dowork(object obj)
{

int para = (int)obj_ocr;

..... //循环代码

}

#endregion

#region 调用线程

/// <summary>
/// 调用线程
/// </summary>
public void ThreadPro()

{

int para;

Thread t = new Thread(new ParameterizedThreadStart(dowork)); //创建带参数传递的线程
t.Start(para); //传入参数

}

MFC:

.h:

class CLoadingDataDlg : public CDialog
{

public:

void LoadingData();

static UINT ProcThread(LPVOID Para);
UINT Proc(LPVOID Para);

afx_msg void OnBnClickedButtonLoading();

};

cpp:

void CLoadingDataDlg::LoadingData()
{

......

}

UINT CLoadingDataDlg::ProcThread(LPVOID Para)
{
UINT result=0;
CLoadingDataDlg *pObj=(CLoadingDataDlg*)Para;
if(pObj)
{
result=pObj->Proc(NULL);
}
return result;
}

UINT CLoadingDataDlg::Proc(LPVOID Para)
{
LoadingData();
return 0;
}

void CLoadingDataDlg::OnBnClickedButtonLoading()
{
AfxBeginThread(ProcThread,this);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: