在线程中调用SaveFileDialog

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

<span style="color:#4b4b4b">    <span style="color:blue">class </span><span style="color:#2b91af">Program</span><span style="color:black">{</span><span style="color:blue">static void </span><span style="color:black">Main(</span><span style="color:blue">string</span><span style="color:black">[] args){</span><span style="color:#2b91af">Thread </span><span style="color:black">t = </span><span style="color:blue">new </span><span style="color:#2b91af">Thread</span><span style="color:black">(SaveFileAsyn);            t.Start();}</span><span style="color:blue">static void </span><span style="color:black">SaveFileAsyn(){</span><span style="color:blue">var </span><span style="color:black">dialog = </span><span style="color:blue">new </span><span style="color:#2b91af">SaveFileDialog</span><span style="color:black">();dialog.ShowDialog();}}  </span></span>
<span style="color:#4b4b4b"><span style="color:black">直接看上述代码,我们不会觉得有什么问题。</span><span style="color:black">但是,在运行程序时,你会遇到“未处理ThreadStateException”的异常。噢,shit~。</span></span>
<span style="color:#4b4b4b"><a data-cke-saved-href="https://images0.cnblogs.com/blog/4074/201402/081622028455689.png" href="https://images0.cnblogs.com/blog/4074/201402/081622028455689.png"><img alt="image" data-cke-saved-src="https://images0.cnblogs.com/blog/4074/201402/081622035944060.png" src="https://images0.cnblogs.com/blog/4074/201402/081622035944060.png" width="444" height="356" /></a></span>
<span style="color:#4b4b4b"><span style="color:black">不过,仔细检查,你会发现在异常消息中有“请确保您的 Main 函数带有 STAThreadAttribute 标记”的提示,你会感觉找到一丝希望,你迫不及待的按照提示去做,就像这样。</span></span>
<span style="color:#4b4b4b">    <span style="color:blue">class </span><span style="color:#2b91af">Program</span><span style="color:black">{[</span><span style="color:#2b91af">STAThread</span><span style="color:black">]</span><span style="color:blue">static void </span><span style="color:black">Main(</span><span style="color:blue">string</span><span style="color:black">[] args){</span><span style="color:#2b91af">Thread </span><span style="color:black">t = </span><span style="color:blue">new </span><span style="color:#2b91af">Thread</span><span style="color:black">(SaveFileAsyn);t.Start();}</span><span style="color:blue">static void </span><span style="color:black">SaveFileAsyn(){</span><span style="color:blue">var </span><span style="color:black">dialog = </span><span style="color:blue">new </span><span style="color:#2b91af">SaveFileDialog</span><span style="color:black">();dialog.ShowDialog();}}</span></span>

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

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

<span style="color:#4b4b4b">        <span style="color:blue">static void </span><span style="color:black">Main(</span><span style="color:blue">string</span><span style="color:black">[] args){</span><span style="color:blue">var </span><span style="color:black">dialog = </span><span style="color:blue">new </span><span style="color:#2b91af">SaveFileDialog</span><span style="color:black">();dialog.ShowDialog();}
</span></span>

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

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

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

<span style="color:#4b4b4b">    <span style="color:blue">class </span><span style="color:#2b91af">Program</span><span style="color:black">{</span><span style="color:blue">static void </span><span style="color:black">Main(</span><span style="color:blue">string</span><span style="color:black">[] args){</span><span style="color:#2b91af">Thread </span><span style="color:black">t = </span><span style="color:blue">new </span><span style="color:#2b91af">Thread</span><span style="color:black">(SaveFileAsyn);t.SetApartmentState(</span><span style="color:#2b91af">ApartmentState</span><span style="color:black">.STA); </span><span style="color:green">// 设置为单线程单元(STA)状态</span><span style="color:black">t.Start();}</span><span style="color:blue">static void </span><span style="color:black">SaveFileAsyn(){</span><span style="color:blue">var </span><span style="color:black">dialog = </span><span style="color:blue">new </span><span style="color:#2b91af">SaveFileDialog</span><span style="color:black">();dialog.ShowDialog();}} </span></span>

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

单元状态

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

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

<span style="color:#4b4b4b"><span style="color:black">    [</span><span style="color:#2b91af">Serializable</span><span style="color:black">][</span><span style="color:#2b91af">ComVisible</span><span style="color:black">(</span><span style="color:blue">true</span><span style="color:black">)]</span><span style="color:blue">public enum </span><span style="color:#2b91af">ApartmentState</span><span style="color:black">{</span><span style="color:green">// System.Threading.Thread 将创建并进入一个单线程单元。</span><span style="color:black">STA = 0,</span><span style="color:green">// System.Threading.Thread 将创建并进入一个多线程单元。</span><span style="color:black">MTA = 1,</span><span style="color:green">// 尚未设置 System.Threading.Thread.ApartmentState 属性。</span><span style="color:black">Unknown = 2,}
</span></span>

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. DllMain中不当操作导致死锁问题的分析——线程中调用GetModuleFileName、GetModuleHandle等导致死锁

    之前的几篇文章已经讲解了在DllMain中创建并等待线程导致的死锁的原因.是否还记得,我们分析了半天汇编才知道在线程中的死锁位置.如果对于缺乏调试经验的同学来说,可能发现这个位置有点麻烦.那么本文就介 ...

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

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

  3. springboot项目在线程中调用service访问数据库

    创建类ApplicationBeanFactory package com.xxx.comm.ApplicationBeanFactory;import org.springframework.bea ...

  4. 在线程中调用OpenFileDialog/SaveFileDialog报错处理方法

    当在一个线程中使用 OpenFileDialog ofd = new OpenFileDialog(); ofd.ShowDialog(); 会报如下错误: 在可以调用 OLE 之前,必须将当前线程设 ...

  5. aardio学习笔记-在线程中调用数据

    废话不多说,直接开干! 有个需求就是在线程中,获取数据 于是乎,线程只有匿名行数 请看文档代码! //打开控制台 io.open()//定义一个函数 func = function(){ return ...

  6. Android入门第37天-在子线程中调用Handler

    简介 前一章我们以一个简单的小动画来解释了Handler. 这章我们会介绍在子线程里写Handler.如果是Handler写在了子线程中的话,我们就需要自己创建一个Looper对象了:创建的流程如下: ...

  7. Java线程 构造函数在那个线程中调用

    在学习Java的ThreadLocal机制时,偶然发现这个问题,所以记录下来 public class ThreadConstruct {public static void main(String[ ...

  8. 在线程中调用PJSIP中的呼叫出现提示注册线程pj_thread_register的解决方法

    在PJSIP的相关函数中(例如pjsua_call_make_call等),都增加了线程注册的判断, 下面以pjsua_call_make_call为例说明: 如果执行pjsua_call_make_ ...

  9. MFC子线程中调用updatedata()函数报错解决方法

    在XXXDlg.h顶部添加 #define WM_UPDATEDATA WM_USER+100 关联自定义消息,在XXXDlg.cpp中消息处理函数中添加 ON_MESSAGE(WM_UPDATEDA ...

最新文章

  1. react引入多个图片_重新引入React:v16之后的每个React更新都已揭开神秘面纱。
  2. Android 轮询最佳实践 Service + AlarmManager+Thread
  3. 深度洞见|起底元宇宙风潮,如何重塑未来数字营销?
  4. boost::hana::make_range用法的测试程序
  5. 模式的秘密-观察者模式(二)
  6. modbus 台达a2_驱控智造未来 台达重磅发布多款工业自动化新品
  7. ANT出现“警告: 编码 GBK 的不可映射字符”解决方法
  8. jquery access方法 有什么用
  9. linux getopt_long函数,新手疑问:getopt_long()重入问题
  10. 1.1音响系统放大器设计
  11. 利用pytorch实现图像分类
  12. 企业微信群发消息提醒
  13. C语言,移动鼠标获得当鼠标当前位置坐标
  14. 删除的vue怎么找回_vue详情 恢复 删除
  15. cpy几天爬出密道问题
  16. 云服务器微信faq,微信公众平台常见问题FAQ
  17. 单细胞分析:质控实操(五)
  18. Oracle--rename
  19. 利用FME自动生成CAD图框
  20. spring data redis 配置

热门文章

  1. 不仅仅是看片,娱乐宝要让粉丝来投资电影
  2. 微软封堵IE漏洞:曾被黑客疯狂利用
  3. 沈坤荣《宏观经济学教程》第3版课后答案
  4. 那些治愈者会再一次感染新冠病毒吗?
  5. 什么是高防IP以及私服被攻击了的处理方案
  6. 电控针阀和手动可变泄漏阀组合在超高真空度精密PID控制中的应用
  7. [java]PixelShader:2d骨骼动画图帧编辑器
  8. 如何万网域名解析亚马逊服务器,使用aws ELB后关于proxy_pass 的域名解析问题
  9. js中onmouseover事件不起作用
  10. 常用算法(七)——克鲁斯卡尔算法