在多线程编程中,有时候可能需要在单独线程中执行某些操作。例如,调用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. InnoDB的WAL方式学习

    之前写过一篇博文,<不好的MySQL过程编写习惯>(http://www.cnblogs.com/wingsless/p/5041838.html).这篇博文里强调了不要循环的提交事务,尽 ...

  2. 【Unity】改变向量的方向而不改变其大小

    最近在做一个打砖块游戏时遇到一个小问题,就是小球有可能会在左右两个边界之间做循环往返运动而导致游戏无法继续进行下去,于是我打算让小球在垂直撞向边界时改变一下方向,但是速度不变,尝试了一些方法但是没有达 ...

  3. Makefile变量

    自定义变量 = 是最基本的赋值,会把整个makefile展开之后再决定是多少 x=foo y=$(x)bar #y是asdbar,不是foobar x=asd := 是覆盖之前的值,和=不同,和赋值的 ...

  4. LeetCode#227.Basic Calculator II

    题目 Implement a basic calculator to evaluate a simple expression string. The expression string contai ...

  5. C++杂谈(二)初识vector容器与迭代器

    教科书中失踪的vector 很奇怪的一件事情,在当时学习C++的时候,老师并没有讲授容器的内容,当时参考的谭浩强老师的红皮C++也没有这个内容,不知为何.后来再学C++,发现容器是一个很重要的概念,在 ...

  6. Linux vi 操作命令整理

    转自:http://www.lupaworld.com/?uid-296380-action-viewspace-itemid-118973   vi/vim 基本使用方法 本文介绍了vi (vim) ...

  7. HashMap的工作原理深入再深入

    前言 首先再次强调hashcode (==)和equals的真正含义(我记得以前有人会说,equals是判断对象内容,hashcode是判断是否相等之类): equals:是否同一个对象实例.注意,是 ...

  8. web测试方法

    首先互联网B/S系统一般分为三层,即表示层.业务逻辑层.数据层,下面是我整理的关于web的测试方法. 表示层 一.功能测试 1.链接测试 确认每个链接有效且正确跳转 2.表单测试 确认表单能正常提交, ...

  9. JavaWeb学习总结,文件上传和下载

    在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用 ...

  10. Sql-Server应用程序的高级注入

    本文作者:Chris Anley 翻译: luoluo [luoluonet@hotmail.com] [目 录] [概要] [介绍] [通过错误信息获取信息] [更深入的访问] [xp_cmdshe ...