惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
有赞技术团队
有赞技术团队
T
The Blog of Author Tim Ferriss
F
Fortinet All Blogs
D
DataBreaches.Net
F
Full Disclosure
腾讯CDC
博客园 - 【当耐特】
MyScale Blog
MyScale Blog
Stack Overflow Blog
Stack Overflow Blog
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
Last Week in AI
Last Week in AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
爱范儿
爱范儿
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
S
SegmentFault 最新的问题
The Register - Security
The Register - Security
WordPress大学
WordPress大学
博客园 - 聂微东
雷峰网
雷峰网
J
Java Code Geeks
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy International News Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
A
Arctic Wolf
Scott Helme
Scott Helme
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tor Project blog
博客园 - 三生石上(FineUI控件)
Know Your Adversary
Know Your Adversary
AWS News Blog
AWS News Blog
G
Google Developers Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
C
CERT Recently Published Vulnerability Notes
O
OpenAI News
Project Zero
Project Zero
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Application and Cybersecurity Blog
Application and Cybersecurity Blog
云风的 BLOG
云风的 BLOG
N
News and Events Feed by Topic
MongoDB | Blog
MongoDB | Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
Cisco Talos Blog
Cisco Talos Blog
P
Palo Alto Networks Blog
Schneier on Security
Schneier on Security

博客园 - zsi

DSOFramer 之一:在 64 位系统注册 DSOFramer GridView 绑定数据不满一页时填充空行的方法 GridView 始终显示 Pager 分页行的一种方法 Chrome: Google加入浏览器大战之兼容性 调用unrar.dll时SEHException外部组件异常的处理 ASP.NET 2.0无法打开到 SQL Server 的连接 扯扯OpenFileDialog和.NET的缺省目录 给ASP.NET程序换换地儿 对象序列化:经验小结 对象序列化:使用XmlSerializer走完最后一步 对象序列化:使用System.Xml.Serialization命名空间 在.NET中实现对象序列化 了解HTTP协议一些有用资料 Yahoo!十岁! 在VB.NET中处理构造函数时值得注意的两个陈述 微软新发布的共享设计模式的WIKI 还不快进入Design Pattern的世界? 另人费解的IsNot关键字 也说金山词霸2005内存泄露的问题
在线程中调用SaveFileDialog
zsi · 2014-02-10 · via 博客园 - zsi

在多线程编程中,有时候可能需要在单独线程中执行某些操作。例如,调用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~。
image
不过,仔细检查,你会发现在异常消息中有“请确保您的 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单线程单元。