在多线程编程中,有时候可能需要在单独线程中执行某些操作。例如,调用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. W3School-CSS 字体(font)实例

    CSS 字体(font)实例 CSS 实例 CSS 背景实例 CSS 文本实例 CSS 字体(font)实例 CSS 边框(border)实例 CSS 外边距 (margin) 实例 CSS 内边距 ...

  2. 批处理脚本为Mysql重置root密码(重置密码为123456)

    @echo off title mysql ::从注册表找到Mysql的安装路径写入文件mysql.txt reg query HKLM\SYSTEM\ControlSet001\Services\M ...

  3. Java设计模式 - 适配器模式

    概念: 将一个类的接口,转换成客户期望的另一个接口.适配器模式让原来接口不兼容的类可以在一起工作. 解决的问题: 提供类似于中间人的作用:把原本不兼容.不能一起工作的接口组合在一起,使得它们能够在一起 ...

  4. 烂泥:puppet添加带密码的用户

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 前一篇文章,我们介绍了有关puppet3.7的安装与配置,这篇文章我们再来介绍下如何利用puppet添加带密码的用户. 要通过puppet添加带密码的用 ...

  5. python 的异常及其处理

    Python 异常处理 python提供了两个非常重要的功能来处理python程序在运行中出现的异常和错误.你可以使用该功能来调试python程序. 异常处理: 本站Python教程会具体介绍. 断言 ...

  6. IO的多路复用和信号驱动

    Linux为多路复用IO提供了较多的接口,有select(),pselect(),poll()的方式,继承自BSD和System V 两大派系. select模型比较简单,“轮询”检测fd_set的状 ...

  7. Android初涉及之Android Studio&JAVA入门--二月不能不写东西

    是的,我还没有放弃写博客. 是的,我也没有放弃PHP的学习. 是的,我要开始学学最TM火的Android开发了. 你呢 1.Android Studio 一.概况 安装和配置什么的就不具体说了,网上一 ...

  8. 关于Lucene 3.0升级到Lucene 4.x 备忘

    最近,需要对项目进行lucene版本升级.而原来项目时基于lucene 3.0的,很古老的一个版本的了.在老版本中中,我们主要用了几个lucene的东西: 1.查询lucene多目录索引. 2.构建R ...

  9. RSA Study

    These days I study the RSA Algorithm. It is a little complex, but not very. Also, my study has not f ...

  10. c++学习之容器细枝末节(1)

    对照着c++primier 开始学习第九章容器,把课后习题当做练习,虽然是看过书上的讲解,但是做题编程的时候,一些需要注意的地方还是难免有遗漏. 一下是几点印象比较深刻的总结: (1)前几章只学了ve ...