跨线程操作Form时,必须在当前线程创建(包括Form.Show),还是 Form.Show 的时候必须在当前线程?

oldhunter 2019-03-14 03:25:57
跨线程操作Form时,必须在当前线程创建(包括Form.Show),还是 Form.Show 的时候必须在当前线程?

之前的作法是创建Form对象,没有使用“工作线程”,但显示窗体(Form.Show)以及后续对Form的操作,都是使用的“工作线程”,即通过 Control.Invoke 的方式进行显示与后续操作。

也就是说 Form form = new Form() 没有在“工作线程”,但 form.Show 及其它操作在“工作线程”。

这样做之前没有问题,但最近发现程序有些异常,界面UI经常无故卡住,感觉可能跟这个有关系。想问一下,准确规范是什么样的?

Form form = new Form(),有没有必要在“工作线程”中创建? 不考虑 CheckForIllegalCrossThreadCalls = false 的情况。
...全文
410 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
xiaoyety 2019-03-16
  • 打赏
  • 举报
回复
wanghui0380 2019-03-14
  • 打赏
  • 举报
回复
  //方法一:当前线程调度器
Task.Factory.StartNew(() =>
{
this.label1.Text = "方法一";
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

//方法二:利用await进行上下文切换;
this.label1.Text= await Task<string>.Run(() => "方法二"); //把复杂计算丢到default,await会进行上下文切换;

//方法三:手动显式切换上下文
Task<string>.Run(() => "方法三").ContinueWith(p => { this.label1.Text = p.Result; },
/ TaskScheduler.FromCurrentSynchronizationContext());

//方法四:直接跨越使用上下文同步
SynchronizationContext uiContext=SynchronizationContext.Current;
Task.Run(() =>
{
uiContext.Post(state =>
{
this.label1.Text = "方法四";
},null);
});

//方法五:invoke,begininvoke 这个我就不写了,楼上说的很多了
  • 打赏
  • 举报
回复
引用 6 楼 oldhunter 的回复:
之前不是这样写的,为什么程序不报错? 不是应该每次都要报错的吗? 我也没有设置 CheckForIllegalCrossThreadCalls = false
new Form() 不报错,但是后续就乱了——报错了。
  • 打赏
  • 举报
回复
引用 1 楼 oldhunter 的回复:
因为之前的理解是,form.Show 后,WINDOWS的消息泵才开始工作,所以理解为 Form form = new Form() 在哪个线程都可以
我是无法理解这个推理的。
  • 打赏
  • 举报
回复
引用 楼主 oldhunter 的回复:
跨线程操作Form时,必须在当前线程创建(包括Form.Show),还是 Form.Show 的时候必须在当前线程? 之前的作法是创建Form对象,没有使用“工作线程”,但显示窗体(Form.Show)以及后续对Form的操作,都是使用的“工作线程”,即通过 Control.Invoke 的方式进行显示与后续操作。 也就是说 Form form = new Form() 没有在“工作线程”,但 form.Show 及其它操作在“工作线程”。
其实并非“当前线程”,而是对控件的操作应该用创建控件的那个线程。说“当前”其实是容易歧义的,因为你这个“当前”飘忽不定! 所以,如果你把正你 new Form() 的时候使用之前创建的 UI 线程来创建它,那么参照点就一直是主窗体线程,没有问题。而如果你 new Form() 的时候根本就是飘忽不定的,那么后边很可能越来越乱。
wanghui0380 2019-03-14
  • 打赏
  • 举报
回复
线程有上下文环境。

要更新UI的线程就得放在UI环境,所以之前能,现在不能。只是因为之前是在UI的Context上

有关这块,请看TaskScheduler线程调度器的使用。

Task.Factory.StartNew 默认在TaskScheduler.Default 线程池里,而非在当前上下文上
zhuowp 2019-03-14
  • 打赏
  • 举报
回复
引用 7 楼 oldhunter 的回复:
[quote=引用 2 楼 zhuowp 的回复:]
都需要。凡是和ui相关的操作,都需要放到ui线程里执行。

在winform里面,可以通过 ui控件.Invoke(),将ui操作的东西委托给ui线程。


之前不是这样写的,为什么程序不报错? 不是应该每次都要报错的吗? 我也没有设置 CheckForIllegalCrossThreadCalls = false
[/quote]要跨线程操作才需要这样。比如你用Task.Factory.StartNew(() =>{})或者 new Thread()这样的。

如果你的调用线程本身就是ui线程,比如在鼠标点击事件中去操作,就不需要。
oldhunter 2019-03-14
  • 打赏
  • 举报
回复
引用 2 楼 zhuowp 的回复:
都需要。凡是和ui相关的操作,都需要放到ui线程里执行。

在winform里面,可以通过 ui控件.Invoke(),将ui操作的东西委托给ui线程。


之前不是这样写的,为什么程序不报错? 不是应该每次都要报错的吗? 我也没有设置 CheckForIllegalCrossThreadCalls = false
oldhunter 2019-03-14
  • 打赏
  • 举报
回复
之前不是这样写的,为什么程序不报错? 不是应该每次都要报错的吗? 我也没有设置 CheckForIllegalCrossThreadCalls = false
zhuowp 2019-03-14
  • 打赏
  • 举报
回复
引用 4 楼 oldhunter 的回复:
[quote=引用 3 楼 zhuowp 的回复:]
甚至,你的某个ui控件绑定了后台数据的某个属性,通过改变这个属性会引起ui的变化,那么给这个属性赋值的时候也要在ui线程中执行。


另外,如果不是设置某个属性,只是读取一下属性,也要用 Invoke 吗?[/quote]如果你读取的是控件的属性,比如Width,Height这些的,需要。
oldhunter 2019-03-14
  • 打赏
  • 举报
回复
引用 3 楼 zhuowp 的回复:
甚至,你的某个ui控件绑定了后台数据的某个属性,通过改变这个属性会引起ui的变化,那么给这个属性赋值的时候也要在ui线程中执行。


另外,如果不是设置某个属性,只是读取一下属性,也要用 Invoke 吗?
zhuowp 2019-03-14
  • 打赏
  • 举报
回复
甚至,你的某个ui控件绑定了后台数据的某个属性,通过改变这个属性会引起ui的变化,那么给这个属性赋值的时候也要在ui线程中执行。
zhuowp 2019-03-14
  • 打赏
  • 举报
回复
都需要。凡是和ui相关的操作,都需要放到ui线程里执行。

在winform里面,可以通过 ui控件.Invoke(),将ui操作的东西委托给ui线程。
oldhunter 2019-03-14
  • 打赏
  • 举报
回复
因为最近有一个错误提示:在某个线程上创建的控件,不能成为在另一个线程上创建的控件的父级

控件的创建,如何理解? Form form = new Form() 算是创建,还是 form.Show() 算是创建?

因为之前的理解是,form.Show 后,WINDOWS的消息泵才开始工作,所以理解为 Form form = new Form() 在哪个线程都可以,只有
form.Show() 的时候,才算是确定线程。
在码农的生活中,很多级码农都有这样的经历,会被一个小小的技术问题拦住,然后进度跟不上了,被老板XXXX一大通了。心情不爽了。 好吧,这个曾经是我遇到拦路虎之一。但事实上不是什么大技术。技术就是一层纸,破了就破了。 这是一个关于如何窗体操作控件或过程的一个例子。比如,你想用窗体A的按键来执行窗体B的文本框变色。 Imports System Imports System.Threading Imports System.Text Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load CheckForIllegalCrossThreadCalls = False '不写这行,会出错,不允许线程的数据写到TextBox1.Text 中去。 Form2.Show() End Sub Private Sub form1_FormClosing(sender As Object, e As EventArgs) Handles Me.FormClosing ' If runThread.IsAlive = True Then runThread.Abort() End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click '用的是委托方式 Me.Invoke(New MethodInvoker(AddressOf THREAD2)) End Sub Private Sub THREAD2() Static j As Integer j = j + 1 TextBox1.Text = " 这是 [线程] 操作" & vbCrLf & _ " Button2被点了: " & j & " 次" & vbCrLf & "要求是from2.textbox.text= textbox1.text 。[问题]但为什么不能成功显示呢?" End Sub Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged Form2.TextBox1.Text = TextBox1.Text End Sub Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click Static j As Integer j = j + 1 TextBox1.Text = " 这是 [非线程] 操作" & vbCrLf & _ "Button3 点击了: " & j & " 次" & vbCrLf & _ "要求是from2.textbox.text= textbox1.text, 可以成功显示,这个是对的。" End Sub End Class
【版本已更新:http://download.csdn.net/source/1687395】 半成品,还有以下几部分未完成: 1、断点续传;(方法都在,代码没调好,就先注释掉了) (就是保存当前下载信息,下次重新读取,最简单的做法就是序列化,要用到的自己改改调调就成) 2、自定义下载窗体; (就像 MessageBox.Show() 一样,已经做了一个简单的Form) 3、部分细节没做好,可能存在BUG; (用着,遇到再说) 涉及的一些应用: 多线程 -- 没用线程池,因为不好控制状态; 事件驱动 -- 自我感觉不是很好,最好谁能改改; 另外,组件的封装也没仔细整理过~ 期待有人能做个完美的组件。 应用范围:文件下载、在线升级 (这个组件就是为了下载升级文件而做的……) 使用示例:(添加DLL引用) List DTaskList = new List(); foreach ( …… ) { DownloadMag.DTask dt = new DTask(); dt.Name = "任务名"; dt.FileName = "文件名"; dt.Size = 文件大小; dt.URL = "下载地址"; dt.CRC32 = CRC32校验值; dt.SaveMode = true; DTaskList.Add(dt); } new DownloadMag.DownloadForm(this).Show(DTaskList.ToArray(), "下载信息文件名"); 没间修修改改,所以把这个半成品放出来了,也就当抛砖引玉吧。 谁有兴趣有空闲就提提意见,找找Bug,最好是把它做完美了 :) 意见请提到:http://blog.csdn.net/0xff/archive/2007/11/01/1861780.aspx

110,538

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧