在多线程编程中,有时候可能需要在单独线程中执行某些操作。例如,调用SaveFileDialog类保存文件。首先,我们在Main方法中创建了一个新线程,并将其指向要执行的委托SaveFileAsyn。在SaveFileAsyn方法中,我们像平时做的一样,声明一个SaveFileDialog的新实例,并调用ShowDialog方法显示文件保存对话框。

    class Program
{
static void Main(string[] args)
{
Thread t = new Thread(SaveFileAsyn);
t.Start();
} static void SaveFileAsyn()
{
var dialog = new SaveFileDialog();
dialog.ShowDialog();
}
}
直接看上述代码,我们不会觉得有什么问题。但是,在运行程序时,你会遇到“未处理ThreadStateException”的异常。噢,shit~。
不过,仔细检查,你会发现在异常消息中有“请确保您的 Main 函数带有 STAThreadAttribute 标记”的提示,你会感觉找到一丝希望,你迫不及待的按照提示去做,就像这样。

    class Program
{
[STAThread]
static void Main(string[] args)
{
Thread t = new Thread(SaveFileAsyn);
t.Start();
} static void SaveFileAsyn()
{
var dialog = new SaveFileDialog();
dialog.ShowDialog();
}
}

然后,重新运行程序,期望讨厌的异常走开。但是,很不幸的是,你依然会遇到同样的“未处理ThreadStateException”的异常。你会很奇怪,觉得微软欺骗了你。你遇到了异常,你按照提示的那样修改代码,但是程序却没有像你想象的那样运行,你一定很恼火。是的,我可以确定,我当时非常非常的恼火!

但是,这里的问题是,“请确保您的 Main 函数带有 STAThreadAttribute 标记”是一个过于直接的,以致于令人感觉带有欺骗性的提示。

        static void Main(string[] args)
{
var dialog = new SaveFileDialog();
dialog.ShowDialog();
}

如果我们的代码像上面一样,直接在Main方法中SaveFileDialog类的ShowDialog方法,那么当我们添加STAThread标记后,确实是可以解决问题的(感兴趣的朋友可以自己试一下:)。而我们之前的代码,是在一个新建的Thread线程中执行SaveFileDialog类的ShowDialog方法。

添加STAThread标记解决的,是在主线程调用SaveFileDialog类的ShowDialog方法的问题,而不能直接解决在新建Thread线程中调用ShowDialog方法的问题。但是,它提供了一些有益的线索。

其实,在上面的异常消息中,最重要的是“必须将当前线程设置为单线程单元(STA)模式”这句话。在Main方法上添加STAThread标记是解决方法之一。它解决的是将主线程设置为单线程单元的问题,而不是解决将新建线程设置为单线程单元的问题。这就是为什么我们直接在Main方法上添加STAThread标记后,仍然提示同样错误的原因。所以,要解决我们最初的问题,就必须将新建线程设置为单线程单元。我们可以使用Thread类的SetApartmentState方法将线程设置为STA单线程单元状态。

    class Program
{
static void Main(string[] args)
{
Thread t = new Thread(SaveFileAsyn);
t.SetApartmentState(ApartmentState.STA); // 设置为单线程单元(STA)状态
t.Start();
} static void SaveFileAsyn()
{
var dialog = new SaveFileDialog();
dialog.ShowDialog();
}
}

将新建线程的单元状态设置为STA单线程单元后,就可以在新建线程中运行SaveFileDialog类的ShowDialog方法了。

单元状态

那么,什么是单元状态呢?单元状态是COM组件用于同步资源访问的一种机制。.NET Framework本身不需要单元状态。但是,当与COM对象进行互操作时,则必须创建并初始化Thread线程的单元状态。STA单线程单元通常在UI组件中使用,Windows窗口消息即是其中之一。

下面是.NET类库中ApartmentState枚举的定义。ApartmentState枚举包含STA、MTA和Unknown3个成员。Thread线程初始化后,其ApartmentState单元状态默认是MTA,即多线程单元。

    [Serializable]
[ComVisible(true)]
public enum ApartmentState
{
// System.Threading.Thread 将创建并进入一个单线程单元。
STA = 0,
// System.Threading.Thread 将创建并进入一个多线程单元。
MTA = 1,
// 尚未设置 System.Threading.Thread.ApartmentState 属性。
Unknown = 2,
}

SaveFileDialog作为.NET Framework默认的几种对话框之一,目的是为开发人员提供与Windows操作系统一致的用户体验的组件,其底层调用的是Windows操作系统的COM组件。也就是说,SaveFileDialog是对底层COM组件的封装,与SaveFileDialog交互,就是与底层COM组件交互。因此,调用SaveFileDialog类的Thread线程的单元状态必须是STA单线程单元,需要使用SetApartmentState方法将其单元状态设置为ApartmentState.STA。

结论

通过上面的介绍,我们对线程的单元状态有了初步了解,大致可以归纳为以下几点:

  1. .NET Framework本身不需要单元状态。
  2. Thread线程的单元状态默认为MTA多线程单元。
  3. 可以通过Thread线程实例的SetApartmentState方法设置线程的单元状态。
  4. 如需与COM对象进行互操作,必须创建并初始化Thread线程的单元状态。
  5. 如需操作的COM对象是UI组件,如Windows窗口,则Thread线程的单元状态必须设置为STA多线程单元。

由此可见,在大多数开发环境中,开发人员无需操心线程的单元状态。只有当与COM对象互操作,并且涉及Windows窗口相关的UI组件时,才需要将线程的单元状态设置为STA单线程单元。

在线程中调用SaveFileDialog的更多相关文章

  1. 为什么说invalidate()不能直接在线程中调用

      1.为什么说invalidate()不能直接在线程中调用?2.它是怎么违背单线程的?3.Android ui为什么说不是线程安全的?4.android ui操作为什么一定要在UI线程中执行? 1. ...

  2. 为何invalidate()不可以直接在UI线程中调用&invalidate与postInvalidate

    1.android ui操作为什么一定要在主线程中执行? 答:Android UI操作是单线程模型,关于UI更新的相关API(包括invalidate())都是按照单线程设计的,对于多线程运行时不安全 ...

  3. 线程中使用SaveFileDialog不能弹出窗体

    在子线程中使用 SaveFileDialog 无法弹出窗体,主要是我们需要用主线程去处理SaveFileDialog , 我们可以将子线程进行如下设置: public partial class Fo ...

  4. 解决django或者其他线程中调用scrapy报ReactorNotRestartable的错误

    官网中关于ReactorNotRestartable的错误描述(摘自:https://twistedmatrix.com/documents/16.1.0/api/twisted.internet.e ...

  5. 线程中调用python win32com

    在python的线程中,调用win32com.client.Dispatch 调用windows下基于COM组件的应用接口, 需要在调用win32com.client.Dispatch前,调用pyth ...

  6. WPF非UI线程中调用App.Current.MainWindow.Dispatcher提示其他线程拥有此对象,无权使用。

    大家都知道在WPF中对非UI线程中要处理对UI有关的对象进行操作,一般需要使用委托的方式,代码基本就是下面的写法 App.Current.MainWindow.Dispatcher.Invoke(ne ...

  7. 线程中调用service方法出错

    public class PnFileTGIComputeThread implements Runnable { @Resource private AppUsedService appUsedSe ...

  8. 线程中调用Updatedata的问题

    随便发个自定义消息,然后在 CMyDialog的自定义消息处理函数中 UpdateDate().因为 UpdateDate用到了线程本地存储.不能跨线程的 UpdateData只能在主线程中使用,将U ...

  9. 在线程中调用其它主界面的模块,因为中间有休息1000ms,所以调用前要检查DateTimeRun变量;在From_load 启动线程;在From_closing From_closed 设置DateTimeRun=false

    //系统启动后,自动启动时钟 void jishi_kernel() { try { while (DateTimeRun) { Thread.Sleep(); if (myRunning) Runn ...

随机推荐

  1. spring项目的 context root 修改之后,导致 WebApplicationContext 初始化两次的解决方法

    修改了 spring web 项目的 context root 为 / 之后,在启动项目时,会导致 WebApplicationContext  初始化两次,下面是其初始化日志: 第一次初始化: 四月 ...

  2. DOS命令:IIS安装与卸载

    //IIS7完全安装 start /w pkgmgr /iu:IIS-WebServerRole;IIS-WebServer;IIS-CommonHttpFeatures;IIS-StaticCont ...

  3. sql server 跟踪各事件的字段项编码及解释

    column_id有如下表:1 TextData 与跟踪内捕获的事件类相关的文本值. 2 BinaryData 与在跟踪中捕获的事件类相关的二进制值. 3 DatabaseID USE databas ...

  4. Hello, AnnsShadow!

    Hello! 发现这个神奇的园子快一年了,自己的学习历程磕磕碰碰也过了一年了,想想,这么久了,是时候做些记录做个分享者了. 从一开始的只敢看Blog,到现在自己发表一下自己的所感所想,算是一种成长了吧 ...

  5. db2服务端安装图解

    一. 准备工作 1. db2服务端安装包,版本:10.1.2 二. 安装图解过程 1. 响应文件是一个包含安装和配置信息的纯英文文本文件.可无需任何用户交互进行db2的批量安装.非必须的. 2. 点击 ...

  6. my_strcat()

    char* my_strcat(char* S1,const char* S2){ //严格符合strcat()的接口形式,需要的S1空间是两个字符串空间总和-1. int i=0,j=0; whil ...

  7. 描述Linux系统开机到登陆界面的启动过程(计时2分钟)

    简述: 1.开机BIOS自检 2.MBR引导 3.grub引导菜单 4.加载内核kernel 5.启动init进程 6.读取inittab文件,执行rc.sysinit,rc等脚本 7.启动minge ...

  8. mysql避免重复插入的三种方法

    在开发中,我们经常遇到这样的需求,如果插入的数据不存在就插入存在就更新(或者不做任何操作).mysql的insert就提供了此功能,不需要我们在自己的业务逻辑代码上做处理.直接用mysql提供的功能来 ...

  9. log4j 日志信息的引入(通用版)——解决项目运行过程中的日志信息

    定义 log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI组件,甚至是套接口服务器.NT的事件记录器.UNIX Syslog守护进程 ...

  10. iTOP-4412嵌入式开发板ioremap控制GPIO寄存器

    转自迅为电子技术论坛:http://bbs.topeetboard.com GPIO 的寄存器通过 ioremap函数转换之后,可以通过直接控制虚拟地址来控制物理地址(寄存器的实际地址),这样就实现 ...