在多线程编程中,有时候可能需要在单独线程中执行某些操作。例如,调用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. 2、软件设计师要阅读的书籍 - IT软件人员书籍系列文章

    软件设计师在项目组中的地位比软件工程师相对要高一些.但是他们所要阅读的书籍差别还是比较大的.同样的,软件设计师也要阅读比较多的书籍,以能够完成项目的任务为目的,同时还要提高自身在项目组中的竞争地位,而 ...

  2. zoj3802:easy 2048 again(状压dp)

    zoj月赛的题目,非常不错的一个状压dp.. 题目大意是一个一维的2048游戏 只要有相邻的相同就会合并,合并之后会有奖励分数,总共n个,每个都可以取或者不取 问最终得到的最大值 数据范围n<= ...

  3. web端和手机端测试有什么不同

    面试中经常被问到web端测试和手机端测试有什么相同点和区别呢?现在总结一下这个问题,如有不对敬请指正 web端和手机端测试有什么区别 1.相同点 不管是web测试还是手机App测试,都离不开测试的相关 ...

  4. 小解系列-解决WebUploader在谷歌浏览器下弹出框打开慢,在Bootstrap模态框内部多次点击才能触发的问题

    WebUploader百度前端团队开源的上传组件,用起来感觉真心不错的,标题的两个问题是我实际使用过程中遇到的问题,经过百度和谷歌查到解决方案, 特分享一下,以供遇到此问题的童靴. 谷歌浏览器弹出框打 ...

  5. css遮罩蒙版效果 分栏效果

    mask遮罩蒙版效果 来看一下效果图: 这是两张原图: 遮罩层图像 注意,白色区域为透明状态   要展示的图像 使用mask之后产生的效果图   首先来解释一下遮罩.蒙版.和PS中的蒙版.Flash中 ...

  6. ubuntu软件管理

    https://www.cnblogs.com/forward/archive/2012/01/10/2318483.html 一.Ubuntu中软件安装方法1.APT方式(联网安装, 需要联网下载软 ...

  7. sas 数据集导出到excel

    PROC EXPORT DATA= Loan.BOM_FILENAME_2      OUTFILE= "D:\output.xls"      DBMS=EXCEL REPLAC ...

  8. docker安装及加速配置

    需要centos7版本,内核版本不低于3.10 [root@localhost ~]# uname -r3.10.0-514.el7.x86_64 第一步:更新当前软件:[root@localhost ...

  9. e557. 在Applet中显示图片

    See also e551 精简的Applet. Image image; public void init() { // Load image image = getImage(getDocumen ...

  10. C#方法中的ref和out

    ref        通常我们向方法中传递的是值.方法获得的是这些值的一个拷贝,然后使用这些拷贝,当方法运行完毕后,这些拷贝将被丢弃,而原来的值不将受到影响.此外我们还有其他向方法传递参数的形式,引用 ...