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

推荐订阅源

量子位
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Y
Y Combinator Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
博客园 - 司徒正美
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
L
LangChain Blog

博客园 - today

赚钱和生活 Tnsping 和TCP/IP 中的ping 的區別 办公自动化漫谈培训有感 SQL Server 64 bit linked server Error with Sql server 32 bit 组合条件查询(初版) 合并DataGrid的单元格 小心使用@@identity 浅谈反射与特性在接口系统中的应用(编码表转化) BuildSql增加了【生成数据库文档】的功能了 SQLServer2000辅助工具版本更新了 我的SQL Server2000辅助工具 (翻译)从底层了解ASP.NET体系结构 (翻译)在C#中定义和使用自己的特性 搜索指定域名的小工具 (翻译)反射的第二部分:Emit (翻译)反射的第一部分:发现和执行 在SqlServer2000的视图中小心使用*符号 我眼中的程序员 一首打油诗
打造迅速响应的用户界面
today · 2007-10-11 · via 博客园 - today

背景


最近抽时间开发了一个生成
SQL脚本和执行脚本的小工具,基本已经完成,但由于生成脚本和执行脚本相对而言是比较耗时的操作,所以原先的单线程模式会短暂的冻结用户界面,由此,为了更好的用户体验,针对这两个耗时的操作引进了多线程模式。我的目标是给这两个操作添加对应的进度条(与用户界面处在不同的Form),显示目前的进度情况,及时把脚本的执行情况反馈给用户。下面,我就依托这个小小的背景,谈一下自己的见解吧。

需要做的工作


首先,需要做的事情是,搞清楚用户界面、进度条以及耗时的操作之间的关系,可以用
UML的序列图表示,如下图所示:

flow.jpg 

从图中已经可以清晰地看到它们三者之间的关系,不过我还是要简单的叙述一下:

1)        用户界面构造并显示进度条。

2)        用户界面异步调用耗时操作。

3)        耗时操作需要及时地把执行情况反馈给用户界面。

4)        用户界面把目前的执行情况通知给进度条。

5)        耗时操作完成时,通知用户界面。

6)        用户界面销毁进度条,提示任务执行完毕。

明确了做什么之后,下一步就可以思考有哪些方法可以帮助我们解决这个问题了。

有哪些方法


不管采取哪些方法,你都需要思考如下几个问题:

1)        用户界面和耗时操作如何异步调用?

2)        耗时操作如果需要参数,那么如何传递参数?

3)        耗时操作如何把当前的执行情况发送给用户界面?

4)        如果耗时操作有返回值(分为每一个小的耗时操作的返回值和整个耗时操作的返回值),那么如何把执行的结果返回给用户界面?

5)        用户界面如何把执行的情况反馈给进度条?

如果这些问题你都已经考虑清楚了,那么这里要解决的问题也就不再是问题了。让我们逐个解答吧。

用户界面和耗时操作如何异步调用?


针对这个问题,我想解决办法有两种,一是自己利用多线程的相关类构建工作者线程,把耗时操作分配给工作者线程,然后在
UI线程里开启工作者线程。第二种方法,使用异步执行委托,这是在UI线程里处理此类问题所特有的方法,使用此方法免去了很多代码,简单好用。

耗时操作如果需要参数,那么如何传递参数?


这个问题的答案就跟你上面选择的方式有关系了,如果你采用的是自己构建多线程,并且需要传递给工作者线程一些参数,你可以采取的办法有:

l         通过耗时操作的公开属性给其传值。

l         通过耗时操作类的构造函数的参数传值。

l         借助于第三方给其传值,如:你可以把耗时操作需要的数据预先存储到一个文件里或者数据库里。

l         混合使用以上的方法。

如果你采用的是异步执行委托的方式,除了以上的办法外,你还可以采取的办法有:

l         通过BeginInvoke传给耗时操作所需要的数据。

借助这些方法均可以帮助你顺利地把参数传递给耗时操作,我想说的是,你的需求决定了你的办法,你的办法决定了你的代码量以及性能,所以,要三思而后行。

耗时操作如何把当前的执行情况发送给用户界面?


由于耗时操作与
UI处于不同的线程,所以UI不能直接得到耗时操作的执行情况,在这里,我们可以利用的办法有:

l         利用耗时操作的事件,把执行情况发送给事件处理者(用户界面)。

l         借助于第三方,把耗时操作的执行情况存储到一个文件里或者数据库里,供用户界面获取。

l         构建消息通道,通过消息通道,把消息发送给对消息感兴趣的接收者。

如何把执行的结果返回给用户界面?


这个问题与上一问题的解决方式差不多,只不过,如果用户界面采用的是异步委托执行的方式与耗时操作衔接的话,可以使用另外一种接收返回的执行结果,设置
BeginInvoke的回调函数,然后在回调函数里调用EndInvoke接收耗时操作返回的结果。

用户界面如何把执行的情况反馈给进度条?


当用户界面得到执行的情况后,它需要把这些信息传给进度条,通过进度条,向用户展示目前耗时操作的执行情况,那么,用户界面是如何把执行的情况反馈给进度条呢?

在这里,我们需要判断用户界面与进度条,是否处在同一个线程里(使用InvokeRequired判断),决定是采用Invoke的方式,还是直接访问。

到这里,我们已经把所有面临的问题逐步剖析了一遍,相信你已经对类似的问题有了自己的解决办法。剩下的事情就是如何在实际的代码中运用这些方法了。

如何解决我的问题?


接下来,针对我的脚本辅助工具中遇到的问题,做一个阐述。由于我的工具里有两个比较耗时的操作,而解决它们的办法都较类似,所以,我就以脚本的产生为例子,说明一下我的解决之道。

首先,定义一个委托,用于异步执行耗时操作即产生脚本,代码如下:

private delegate string CreateSql(ArrayList list,BuildSQL.DataBase.SQLServer server);

然后,我需要做的是:构建进度条,启动耗时操作。在这里,我采用的是异步执行委托的方式启动耗时操作的,而我的耗时操作:产生脚本会在结束的时候返回产生的脚本。代码如下:

private void StartCreateSql(ArrayList list,BuildSQL.DataBase.SQLServer server)

         
{

              
//禁用产生脚本按钮

              
this.btnCreateSql.Enabled = false;

              
//启动进度条

              bar 
= new ProgressBar();

              bar.Title 
= "正在生成脚本";

              bar.StartPosition 
= FormStartPosition.CenterParent;

              bar.Show();

              bar.ShowProgress(list.Count,
0);

              
//开始产生脚本

              BuildSQL.DTO.SQLServer2000.SQLScript sqlScript 
= new BuildSQL.DTO.SQLServer2000.SQLScript();

//绑定事件,获取耗时操作执行的情况

              sqlScript.ProgressChangeEventHandler
+=new BuildSQL.DTO.SQLServer2000.SQLScript.ProgressDelegate(sqlScript_ProgressChangeEventHandler);

              CreateSql sql 
= new CreateSql(sqlScript.CreateSQLScript);

//异步执行委托,并绑定回调函数CreateSqlFinish,目的是在回调函数里获取产生的脚本

              sql.BeginInvoke(list,server,
new AsyncCallback(CreateSqlFinish),sql);

         }

为了获取耗时操作的执行情况,我采用了事件机制,封装在耗时操作类里的代码为:

//定义进度的委托和事件

public  delegate  void ProgressDelegate(object sender,ProgressEventArgs e);

public  event ProgressDelegate ProgressChangeEventHandler;

 

//触发事件

public  int Now

         
{

              
get{return m_now;}

              
set

              
{

                   m_now 
= value;

                   
//触发事件

                   
if(ProgressChangeEventHandler != null)

                   
{

                       ProgressEventArgs e 
= new ProgressEventArgs(m_max,value);

                       ProgressChangeEventHandler(
this,e);

                   }


                   
//任务完成事件

                   
if(value == m_max)

                   
{

                       
if(ProgressFinishEventHandler != null)

                            ProgressFinishEventHandler(
thisnew EventArgs());

                   }


              }


         }


用户界面接收耗时操作的执行情况,并把这些信息发送给进度条,代码如下:

private void sqlScript_ProgressChangeEventHandler(object sender, BuildSQL.DTO.SQLServer2000.ProgressEventArgs e)
        
{
            
if(InvokeRequired)
            
{
                ShowProgressDelegate show 
= new ShowProgressDelegate(bar.ShowProgress);
                show.BeginInvoke(e.Max,e.Now,
null,null);
            }

            
else
            
{
                bar.ShowProgress(e.Max,e.Now);
            }

        }

最后,当耗时操作结束的时候,获取其执行的结果,在这里获取的是产生的脚本,如下:

private void CreateSqlFinish(IAsyncResult result)
        
{
            
if(bar != null)
            
{
                bar.Close();
            }

            
if(MessageBox.Show("脚本产生成功!"== DialogResult.OK)
            
{
                
this.txtScript.Text = ((CreateSql)result.AsyncState).EndInvoke(result);
                
//启用产生脚本按钮
                this.btnCreateSql.Enabled = true;
            }

        }


到此,我的这个工具中所要解决的问题已经解决了。在这里,我想提醒的是,你不必关注代码的细节,而应该关注代码的骨架。当然这些代码如果想运用在其它地方,可能需要重新编写,我想,如果你愿意,那么下一步考虑的事情应该是如何把这些代码抽取到一个类中,更容易的应用到类似这样的需求里。


最后,给大家推荐一下我的这个脚本辅助工具,希望它也可以给你的工作带来便利。文中如果有不对的地方,还请大家批评指正。备注:如果你需要了解更多的信息,你可以阅读msdn的相关文章:通过多线程为基于 .NET 的应用程序实现响应迅速的用户