《C#winform,处理时间过长导致的问题》
问题一:
报错:【CLR 无法从 COM 上下文 0x1a2740 转换为 COM 上下文 0x1a28b0,这种状态已持续 60 秒。拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows 消息的情况下处理一个运行时间非常长的操作。这种情况通常会影响到性能,甚至可能导致应用程序不响应或者使用的内存随时间不断累积。要避免此问题,所有单线程单元(STA)线程都应使用泵式等待基元(如 CoWaitForMultipleHandles),并在运行时间很长的操作过程中定期发送消息。】
答:解决方法是 在Debug -> Exceptions -> Managed Debug Assistants里 去掉ContextSwitchDeadlock一项前面的钩。
问题二:
运行时间过长,UI界面卡死。
答:
方法一:async + await + Task
参考资料:理解Task和async await - RyzenAdorer - 博客园 (cnblogs.com) 【https://www.cnblogs.com/ryzen/p/13938188.html】
C# Task和async/await详解_c# task await-CSDN博客【https://blog.csdn.net/btfireknight/article/details/97766193】
一、Task
【“任务并行库”,TPL(Task Parallel Library)】
在.NET 4.0时候,引入了任务并行库,也就是所谓的TPL(Task Parallel Library),带来了Task类和支持返回值的Task<TResult>,同时在4.5完善优化了使用。Task不一定代表开辟了新线程,可为在线程池上运行,又或是开辟一个后台Thread,又或者没有开辟线程,通过主线程运行任务。
大致对于Task在通过TaskScheduler和TaskCreationOptions设置后对于将任务分配在不同的线程情况,如下图:
二.异步函数async await
async await是C#5.0,也就是.NET Framework 4.5时期推出的C#语法,通过与.NET Framework 4.0时引入的任务并行库,也就是所谓的TPL(Task Parallel Library)构成了新的异步编程模型,也就是TAP(Task-based asynchronous pattern),基于任务的异步模式。
async在方法名的时候,只允许,返回值为void、Task和Task<TResult>,否则会发生编译报错。
ps:
1、异步:
同步和异步主要用于修饰方法。当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法;当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕,我们称这个方法为异步方法。
异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、较耗时的任务设为异步执行,可以提高程序的运行效率。net4.0在ThreadPool的基础上推出了Task类,微软极力推荐使用Task来执行异步任务,现在C#类库中的异步方法基本都用到了Task;net5.0推出了async/await,让异步编程更为方便。
2、线程池:
Task是在ThreadPool的基础上推出的,我们简单了解下ThreadPool。ThreadPool中有若干数量的线程,如果有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用。当线程池中所有的线程都在忙碌时,又有新任务要处理时,线程池才会新建一个线程来处理该任务,如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行。线程池能减少线程的创建,节省开销。
总结:
总之,可以使用Task来替代Thread/ThreadPool,处理本地IO和网络IO任务时 尽量使用async/await来提高任务执行效率。
/// <summary>
/// 方法一:async + await + Task---------------------------------------------------------------------------------
/// </summary>
private async void button1_Click(object sender, EventArgs e)
{
//String message = GetMessage();
textBox1.Text = await GetMessageOne();
}
private void textBox1_TextChanged(object sender, EventArgs e) { }
private async Task<String> GetMessageOne()
{
return await Task<String>.Run(() =>
{
Thread.Sleep(10000);
return "方法一完成!";
});
}
方法二:使用BackgroundWorker组件
参考资料:C#之BackgroundWorker从简单入门到深入精通的用法总结 - Dsw - 博客园 (cnblogs.com)【https://www.cnblogs.com/netserver/p/11363080.html】
1、定义:
BackgroundWorker:处理多线程任务的组件, 轻松让程序告别UI假死。
2、步骤:
在WindowsForm中,拖入一个Label命名为lblPercent,一个ProgressBar命名为pgbPercent,一个Button命名为End, 从VS的工具箱中直接拉一个BackgroundWorker命名为bgWorker.
ps:
(1)在程序运行途中取消正在进行的运算:
在程序界面上添加一个可以随时中止后台进程的按钮BtnCancel,允许用户在执行过程中取消当前的操作(设置 WorkerSupportsCancellation属性为 true,还要在DoWork方法中进行支持)
(2)再次按btnStart,会感觉有两个重叠的进度条在跑:
先判断一下后台操作是否还在运行中:IsBusy
/// <summary>
/// 方法二:使用BackgroundWorker组件-------------------------------------------------------------------------------
/// </summary>
//开始
private void button2_Click(object sender, EventArgs e)
{
bgWorker.WorkerReportsProgress = true;
bgWorker.DoWork += bgWorker_DoWork;
bgWorker.ProgressChanged += bgWorker_ProgressChanged;
bgWorker.RunWorkerCompleted += bgWorker_Completed;
if (!bgWorker.IsBusy)
{
bgWorker.RunWorkerAsync();
}
}
//取消
private void button4_Click(object sender, EventArgs e)
{
bgWorker.CancelAsync();
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
var bgworker = sender as BackgroundWorker;
for (int i = 0; i <= 100; i++)
{
bgworker.ReportProgress(i);
System.Threading.Thread.Sleep(200);
}
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.pgbPercent.Value = e.ProgressPercentage;
this.lblPercent.Text = @"处理进度:" + e.ProgressPercentage.ToString() + @"%";
}
private void bgWorker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
this.lblPercent.Text = "方法二完成!";
}
方法三:Task + 委托(回调函数)
UI线程位于主线程,如果想要在子线程里更新UI状态,必须要将其切换到主线程,最后进行更新操作。UI控件一般会提供Invoke、InvokeRequired,其中InvokeRequired用于判断是否有子线程在更新UI控件,如果有则返回true,Invoke用于将控制权切换到UI线程
/// <summary>
/// 方法三:Task + 委托(回调函数)-------------------------------------------------------------------------------
/// </summary>
private void button3_Click(object sender, EventArgs e)
{
Task task = Task.Run(() =>
{
int max = progressBar1.Maximum;
for (int i = 1; i <= max; i++)
{
UpdateValue(i);
Thread.Sleep(1000);
}
});
}
// 处理线程
private void UpdateValue(int num)
{
if (progressBar1.InvokeRequired)
{
progressBar1.Invoke(new Action<int>(UpdateValue), new object[] { num });
}
else
{
progressBar1.Value = num;
}
}