C#winform,处理时间过长导致的问题

《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、TaskTask<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;
            }
        }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是C# WinForm处理Excel的示例代码: 1. 首先,在WinForm中添加一个“打开Excel”按钮和一个DataGridView控件。 2. 添加以下命名空间: ``` using Excel = Microsoft.Office.Interop.Excel; ``` 3. 在“打开Excel”按钮的Click事件处理程序中添加以下代码: ``` private void btnOpenExcel_Click(object sender, EventArgs e) { // 创建Excel应用程序对象 Excel.Application excelApp = new Excel.Application(); // 打开Excel文件 Excel.Workbook workbook = excelApp.Workbooks.Open(@"D:\test.xlsx"); // 获取第一个工作表 Excel.Worksheet worksheet = workbook.Sheets[1]; // 获取工作表中的数据 Excel.Range range = worksheet.UsedRange; // 将数据加载到DataGridView控件中 object[,] values = range.Value; int rowCount = values.GetLength(0); int colCount = values.GetLength(1); DataTable dt = new DataTable(); for (int i = 1; i <= colCount; i++) { dt.Columns.Add(values[1, i].ToString()); } for (int i = 2; i <= rowCount; i++) { DataRow dr = dt.NewRow(); for (int j = 1; j <= colCount; j++) { dr[j - 1] = values[i, j]; } dt.Rows.Add(dr); } dataGridView1.DataSource = dt; // 关闭Excel应用程序 excelApp.Quit(); } ``` 上述代码会打开名为“test.xlsx”的Excel文件,获取第一个工作表中的数据,并将其加载到DataGridView控件中。 需要注意的是,上述代码使用了Microsoft.Office.Interop.Excel命名空间,因此需要引用Microsoft Excel 16.0 Object Library组件。可以在Visual Studio中右键单击项目,在“添加”->“引用”中找到该组件并添加。 另外,需要注意在使用完Excel应用程序对象后及时调用Quit方法关闭应用程序,否则会出现Excel进程没有正确关闭的问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值