月度归档:2012 年二月

多线程 控制进度条 C# .Net

前言

在我们应用程序开发过程中,经常会遇到一些问题,需要使用多线程技术来加以解决。本文就是通过几个示例程序给大家讲解一下多线程相关的一些主要问题。

执行长任务操作

  许多种类的应用程序都需要长时间操作,比如:执行一个打印任务,请求一个 Web Service 调用等。用户在这种情况下一般会去转移做其他事情来等待任务的完成,同时还希望随时可以监控任务的执行进度。

?/P>

  下面的代码片断示例了当长任务执行时用户界面是如何被更新的。

// 显示进度条
void ShowProgress( int totalStep, int currentStep )
{
_Progress.Maximum = totalStep;
_Progress.Value = currentStep;
}

// 执行任务
void RunTask( int seconds )
{
// 每 1 / 4 秒 显示进度一次
for( int i = 0; i < seconds * 4; i++ )
{
Thread.Sleep( 250 );

// 显示进度条
ShowProgress( seconds * 4, i + 1 );
}
}

private void _btnRun_Click( object sender, System.EventArgs e )
{
RunTask( Convert.ToInt32( _txtSecond.Value ) );
}

 
当我们运行上面的程序,在整个长任务的过程中,没有出现任何问题。这样就真的没有问题了吗?当我们切换应用程序去做其他事情后再切换回来,问题就发生了!主窗体就会出现如下情况:

  这个问题当然会发生,因为我们现在的应用程序是单线程的,因此,当线程执行长任务时,它同时也就不能重画用户界面了。

为什么在我们切换应用程序后,问题才发生呢?这是因为当你切换当前应用程序到后台再切换回前台时,我们需要重画整个用户界面。但是应用程序正在执行长任务,根本没有时间处理用户界面的重画,问题就会发生。

如何解决问题呢?我们需要将长任务放在后台运行,把用户界面线程解放出来,因此我们需要另外一个线程。

线程异步操作

  我们上面程序中执行按钮的Click 处理如下:

private void _btnRun_Click( object sender, System.EventArgs e )
{
RunTask( Convert.ToInt32( _txtSecond.Value ) );
}

 
回想上面刚才问题发生的原因,直到 RunTask 执行完成后返回,Click 处理函数始终不能够返回,这就意味着用户界面不能处理重画事件或其他任何事件。一个解决方法就是创建另外一个线程,代码片断如下:

using System.Threading;

private int _seconds;

// 执行任务工作线程进入点
void RunTaskThreadStart()
{
RunTask( _seconds );
}

// 通过创建工作线程消除用户界面线程的阻塞问题
private void _btnRun_Click( object sender, System.EventArgs e )
{
_seconds = Convert.ToInt32( _txtSecond.Value );

Thread runTaskThread = new Thread( new ThreadStart( RunTaskThreadStart ) );

runTaskThread.Start();
}

 
现在,我们不再需要等待 RunTask 执行完成才能够从 Click 事件返回,我们创建了新的工作线程并让它开始工作、运行。

  runTaskThread.Start(); 将我们新创建的工作线程调度执行并立即返回,允许我们的用户界面线程重新获得控制权执行它自己的工作。现在如果用户再切换应用程序,因为工作线程在自己的 空间执行长任务,用户界面线程被解放出来处理包括用户界面重画的各种事件,我们上面遇到的问题就解决了。

委托异步调用

  在上面的代码中,我们注意到,我们没有给工作线程进入点(RunTaskThreadStart)传递任何参数,我们采用声明一个窗体类的字段 _seconds 来给工作线程传递参数。在某种应用场合不能够给工作线程直接传递参数也是一件非常痛苦的事情。

如何改进呢?我们可以使用委托来进行异步调用。委托是支持传递参数的。这样,就消除了我们刚才的问题,使我们能够消除额外的字段声明和额外的工作线程函数。

如果你不熟悉委托,你可以简单的把它理解为安全的函数指针。采用了委托异步调用,代码片断如下:

// 执行任务的委托声明
delegate void RunTaskDelegate( int seconds );

// 通过创建委托解决传递参数问题
private void _btnRun_Click( object sender, System.EventArgs e )
{
RunTaskDelegate runTask = new RunTaskDelegate( RunTask );

// 委托同步调用方式
runTask( Convert.ToInt16( _txtSecond.Value ) );
}

//通过创建委托解决传递参数问题,通过委托的异步调用消除用户界面线程的阻塞问题
private void _btnRun_Click( object sender, System.EventArgs e )
{
RunTaskDelegate runTask = new RunTaskDelegate( RunTask );

// 委托异步调用方式
runTask.BeginInvoke( Convert.ToInt16( _txtSecond.Value ), null, null );
}

 

多线程安全

  到这里为止,我们已经解决了长任务的难题和传递参数的困扰。但是我们真的解决了全部问题吗?回答是否定的。

我们知道 Windows 编程中有一个必须遵守的原则,那就是在一个窗体创建线程之外的任何线程中都不允许操作窗体。

我们上面的程序就是存在这样的问题:工作线程是在 ShowProgress 方法中修改了用户界面的进度条的属性。那为什么程序运行没有出现问题,运行正常呢?

没有发生问题是因为是现在的Windows XP操作系统对这类问题有非常健壮的解决方法,让我们避免了问题的发生。但是我们现在的程序不能保证在其他的操作系统能够运行正常!

真正的解决方法是我们能够认识到问题所在,并在程序中加以避免。

  如何避免多线程的窗体资源访问的安全问题呢?其实非常简单,有两种方法:

一种方法就是不管线程是否是用户界面线程,对用户界面资源的访问统一由委托完成;

另一种方法是在每个 Windows Forms 用户界面类中都有一个 InvokeRequired 属性,它用来标识当前线程是否能够直接访问窗体资源。我们只需要检查这个属性的值,只有当允许直接访问窗体资源时才直接访问相应的资源,否则,就需要通过 委托进行访问了。

采用第一种安全的方法的代码片断如下:

 

// 显示进度条的委托声明
delegate void ShowProgressDelegate( int totalStep, int currentStep );

// 显示进度条
void ShowProgress( int totalStep, int currentStep )
{
_Progress.Maximum = totalStep;
_Progress.Value = currentStep;
}

// 执行任务的委托声明
delegate void RunTaskDelegate( int seconds );

// 执行任务
void RunTask( int seconds )
{
ShowProgressDelegate showProgress = new ShowProgressDelegate( ShowProgress );

// 每 1 / 4 秒 显示进度一次
for( int i = 0; i < seconds * 4; i++ )
{
Thread.Sleep( 250 );

// 显示进度条
this.Invoke( showProgress, new object[] { seconds * 4, i + 1 } );
}
}


  采用第二种安全的方法的代码片断如下:

// 显示进度条的委托声明
delegate void ShowProgressDelegate( int totalStep, int currentStep );

// 显示进度条
void ShowProgress( int totalStep, int currentStep )
{
if( _Progress.InvokeRequired )
{
ShowProgressDelegate showProgress = new ShowProgressDelegate( ShowProgress );

// 为了避免工作线程被阻塞,采用异步调用委托
this.BeginInvoke( showProgress, new object[] { totalStep, currentStep } );
}
else
{
_Progress.Maximum = totalStep;
_Progress.Value = currentStep;
}
}

// 执行任务的委托声明
delegate void RunTaskDelegate( int seconds );

// 执行任务
void RunTask( int seconds )
{
// 每 1 / 4 秒 显示进度一次
for( int i = 0; i < seconds * 4; i++ )
{
Thread.Sleep( 250 );

// 显示进度条
ShowProgress( seconds * 4, i + 1 );
}
}

至此,我们用了几个示例说明了如何执行长任务、如何通过多线程异步处理任务进度的显示并解决了多线程的安全性等问题。希望能够给大家对理解多线程编程、委托的使用、异步调用等方面提供一些帮助,也希望能和大家进行进一步的沟通和交流。

 

vs2010几个比较怪异的问题

最近使用vs2010,发现了几个比较想不通的地方。

1.net framework4.0不能直接创建web service项目,必须通过.net 3.5建,然后转成4.0;

2.单独选一个网站点右键没有“卸载项目”的选项,但如果同时选多个项目就有这个选项;

将access数据转到mysql

尝试用了一下火车头采集器采了些文章,当想把它们导出到mysql的时候发现只有收费版才有这个功能,本来是想用myodbc连接器导出,但也出现了一些问题,于是想自己动手把辛苦抓下来的内容导到mysql。

大体思路就是用一个循环从access里一条一条地读出来,然后直接再插到mysql表里,具体步骤如下:

1.下载mysql的c#操作库http://cdnetworks-kr-1.dl.sourceforge.net/project/mysqldrivercs/MySQLDriverCS-n-EasyQueryTools/Release%204.0.1/MySQLDriverCS-n-EasyQueryTools-4.0.1-DotNet2.0.exe,虽然从2007年到现在都没更新过,但还可以用。

2.建一windows form程序(控制台也可以),简单地放一按钮,由它激活导出的动作。

[csharp]
public void ReadRows()
{
string mdbPath = "F:\\LocoySpiderV2010SP3_Free_Build2011-05-20\\Data\\36-产品和图片\\SpiderResult.mdb";
string tableName = "[Content]";
DataTable dt = new DataTable();
try
{
DataRow dr;
//1、建立连接
string strConn
= @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + mdbPath;

//MessageBox.Show(strConn);

OleDbConnection odcConnection = new OleDbConnection(strConn);

//2、打开连接
odcConnection.Open();
//建立SQL查询
OleDbCommand odCommand = odcConnection.CreateCommand();
//3、输入查询语句
odCommand.CommandText = "select * from " + tableName;
//建立读取
OleDbDataReader reader = odCommand.ExecuteReader();

Product product = null;
ProductMng pm = new ProductMng();
while (reader.Read())
{
product = new Product();
product.ProductName = reader.GetString(3);
product.Description = reader.GetString(4);
product.Location = reader.GetString(5);
pm.save(product);

}
//关闭连接
reader.Close();
odcConnection.Close();
//return dt;
}
catch(Exception e)
{
//return dt;
}
}

private void button2_Click(object sender, EventArgs e)
{
ReadRows();
}
[/csharp]

ProductMng.cs

[csharp]
class ProductMng
{
MySQLConnection connection = null;

public ProductMng()
{
connection = new MySQLConnection(new MySQLConnectionString("localhost", "goutu", "root", "password").AsString);
connection.Open();
MySQLCommand comm = new MySQLCommand("set names gbk", connection);

comm.ExecuteNonQuery();
}

public void save(Product product)
{
try
{
string sql = "insert into product(product_name,description,location)values(‘"
+ product.ProductName + "’,'"
+ product.Description + "’,'"
+ product.Location + "’)";
MySQLCommand cmd = GetSqlCommand(sql);
cmd.ExecuteNonQuery();
}
catch
{

}
}

private MySQLCommand GetSqlCommand(String sql)
{
return new MySQLCommand(sql, connection);
}
}
[/csharp]

ContextSwitchDeadlock was detected when debugging in Visual Studio 2008

I’ve been having issues with a “ContextSwitchDeadlock was detected” error message popping up when debugging. The message text is a baffling

The CLR has been unable to transition from COM context 0×197060 to COM context 0x196ef0 for 60 seconds. The thread that owns the destination context/apartment is most likely either doing a non pumping wait or processing a very long running operation without pumping Windows messages. This situation generally has a negative performance impact and may even lead to the application becoming non responsive or memory usage accumulating continually over time. To avoid this problem, all single threaded apartment (STA) threads should use pumping wait primitives (such as CoWaitForMultipleHandles) and routinely pump messages during long running operations.

I’ve read a few things about a config file, but I couldn’t get this to work reliably. Instead, you can switch the MDA off by going to:

Debug -> Exceptions -> Managed Debug Assistants

and unchecking the ContextSwitchDeadlock item. Thanks to Scott Munro.

怎样才能专注?

今天本来想网站改出来,却因为看林书豪的视频而引发阅读了一系列nba的视频而到凌晨两点多才睡,刚躺下,女儿又开始哭,导致第二天十一点多才爬起来,浪费了宝贵的半天,同时老婆觉得的一周有六天都对着电脑,想趁着周日休息全家出去逛逛,我也觉得有道理,于是下午又一起去公园转了一圈,晚上回家又因一些零散的小事而误入看电影的迷途中,感觉自己已经真的很缺少自控能力,很容易被一些事情打扰而陷入浪费时间的泥潭。。。真不知道问题出在哪儿?
突然想起大学时一位政治老师说过的一句话:只有工作好才能休息好,也只有休息好才能工作好。我感觉自己似乎没有把握好一个度,没有把有限的生命投入到更重要的事情当中,没有把工作和生活协调好,没有把时间管理好、分配好,没有把零碎的时间集中到一起,没有控制好自己的心态,我真的需要经常地反省自己。。。

解决ASP.NET调用webservice操作超时问题

在调用webservice出现超时问题的,可以尝试如下的方法来解决。
一、
1、web.config配置,里面增加:
[xml]<httpRuntime maxRequestLength="10240" appRequestQueueLimit="100" useFullyQualifiedRedirectUrl="true" executionTimeout="1200" />[/xml]
2、扩大代理类的超时限制,默认是90秒
[csharp]
YourWebService yws = new YourWebService();
yws.Timeout = 1200000; //20分钟
[/csharp]
3、IIS属性-网站 连接超时时间 1200秒
二、
1、修改 app.config 文件,添加如下代码:

请求执行超时时间为600秒(默认为110秒)
2、设置 Web services 的 Timeout 属性
对 XML Web services 的同步调用的超时(以毫秒为单位)。默认为 100000 毫秒。
lywSqCommon.sqsdData.GetData getData = new lywSqCommon.sqsdData.GetData();//GetData 为类名
getData.Timeout=700000;//单位为毫秒
指示 XML Web services 客户端等待同步 XML Web services 请求完成的时间(以毫秒计)。
提示:如果将 Timeout 属性设置为 Timeout.Infinite,则指示该请求无超时。即使 XML Web services 客户端可以将 Timeout 属性设置为无超时,Web 服务器仍可以在服务器端使请求超时。
系统将以上面两项设置的最小者作为操作超时的时间长度。
记得要把 web.config 的 debug 模式关闭:
[xml]
< compilation
defaultLanguage="c#"
debug="false"
/>
[/xml]
如果 debug 模式没有关闭, executionTimeout 会被忽略. 这时候, 如果应用是在单步跟踪的模式下, 根据经验, 超时时间大约是 90 秒(在 machine.config 里设置的, 我猜的^_^), 如果不是在单步跟踪的模式下, 超时时间可能是 20 分钟(也是我猜的, 因为其 session 的缺省超时时间是 20 分钟, 哈). 我懒得找微软的文档作进一步的求证了, 反正我用不着知道 debug 模式下的确切超时时间.

如何保证一个项目可持续集成发布[转]

这俩天稍微有点空,赶紧补补功课。

持续集成就是利用所谓的cc,cruisecontrol工具对我们的项目进行持续的测试,得以保证项目可以保持持续的可发布状态。

但是光靠一个cc,肯定是无法解决这个问题的,还需要一套机制,这个就是敏捷管理。
需要我们把项目的需要开始调研做起,就要专业有深度,过程一丝不苟,进行严格的质量管控和结果输出,对于正式发布上线要严肃认真做好各种意外准备。从代码角度而言,要保证代码必须有测试代码,测试代码必须达到一定的覆盖率才有意义。而测试代码的加入必须意味着我们的项目必须层次清晰,结构分明,即所谓的mvc分层体系,方便测试用例针对各个层次进行不同的场景模拟和测试。

集成员通过配置CruiseControl工具,可以将部分较为简单、不需要人工干预和希望经常重复执行的集成工作交给工具来自动完成。集成员可以配置多个持续集成项目,包括若干个多人同时在开发的子构件、最终发布的集成包等。
CruiseControl在活动时段,循环执行各构建周期,包含:引导初始化—〉检测源码变化—〉集成构建—〉单元测试—〉发布构建和测试结果等步骤。
每当实施员在私有开发工作站上,将源码检入(Checkin)、加入源码控制(Add to Source Control)、或者向集成流(Stream)提交成果(delivery)时,CruiseControl在随后的构建周期循环中,将通过检测源码变化步骤检测到这一变化,CruiseControl此时会等待预定的间隔,看看是否有新的源码变化出现,避免实施员批量检入或加入源码控制时遗漏后续变更;CruiseControl开始调用Ant封装(wrapper)配置文件执行构建,它首先更新目标源码目录下的所有内容(调用ClearCase ccupdate指令),以同步变化的源码,再进行编译、链接,完成预定的冒烟测试,并将结果记录到相应的日志中;CruiseControl在构建完成后,通过e-mail将成功或失败的结果通知提交源码变更的实施员、以及指定的其他人员,并生成构建报告网页,相关人员通过e-mail接受通知的同时,也可以登陆CruiseControl的发布网页来浏览构建报告详细信息。

其他方面就不说了。附上一些关于cc的使用注意点。

CC可能需要用到下面一些工具
Ø CruiseControl
Ø Tomcat 或者 JETTY
Ø VisualSVN server
Ø Svnant
Ø svn-win32-1.6.5.zip
Ø TortoiseSVN(可选)
Ø Eclipse + subclipse插件(可选)

CC主要就是利用JAVA的一些特性和ANT工具以及SVN对我们的项目进行自动编译,自动检验,自动测试,自动生成一些测试报告,自动部署发布的一系列工作的应用程序。

linux下的部署反而相对方便些,在window下需要主要引入SVN的环境变量,否则无法使用SVN自动更新,windown下安装Subversion,然后把bin路径加入到环境变量中区,在cmd中输入svn试试,如果有结果说明SVN环境已经安装成功,然后就可以在config.xml中构建我们的CC环境。

CC主要包括几个东西,一个是时间控制器,用于管理执行的时间要求。一个是ANT的脚本的调用,可以调用具体的项目中的target任务执行。一个是java工具的使用,包括编译、打包等。一个是LOG日志的输出,可以产生各种格式的报表日志。另外支持一些插件,比如htmlmail插件,支持自动发送结果邮件到具体的邮箱。另外CC包括一个控制台,在控制台中可以查看各个项目执行测试的结果。

不说了,这个工具很重要,用过的人都知道的。

转自:http://xosadan.iteye.com/blog/1068938

BitNami Stack之一redmine安装及修改[转]

公司要求在redmine基础上开发FaceProject

下载bitnami-redmine-1.2.1-1-linux-installer.bin后安装倒不麻烦,

麻烦的是安装成功后如何改成FaceProject,

之前在windows 上修改过,现在又要在linux上修改,额滴神,

为了下次再做类似的事时不再忘记,特意写到博客上做个备忘。

在windows上需要修改的有
[bash]
/BitNami Redmine Stack/serviceinstall.bat

/BitNami Redmine Stack/servicerun.bat

/BitNami Redmine Stack/apps/FaceProject/scripts/redmineini.bat

/BitNami Redmine Stack/apps/FaceProject/scripts/serviceinstall.bat

/BitNami Redmine Stack/apps/FaceProejct/conf/redmine.conf

/BitNami Redmine Stack/apache2/conf/httpd.conf
[/bash]
 

而在linux下就不一样了,需要修改的有
[bash]
/opt/redmine-1.2.1-1/ctlscript.sh

/opt/redmine-1.2.1-1/apps/FaceProject/scripts/ctl.sh

/opt/redmine-1.2.1-1/apps/FaceProject/scripts/redmineini.sh

/opt/redmine-1.2.1-1/apps/FaceProject/conf/redmine.conf

/opt/redmine-1.2.1-1/apps/FaceProject/config/mongrel_cluster.yml

/opt/redmine-1.2.1-1/apache2/conf/httpd.conf
[/bash]
 

其他的修改还有很多是FaceProject中具体ruby类的修改了,我曾经尝试着将

windows下已经修改后的FaceProject直接upload到linux下,发现主要不同的地方有

FaceProejct/config(windows下没有mongrel_cluster.yml文件)

和FaceProject/tmp/pids(windows下没有该目录,如果linux无该目录,启动mongrel_rails报错)

其他差别暂时未发现。

 

windows下启动用manager-windows.exe即可管理各个服务的启动与停止

linux下启动关闭用
[bash]
/opt/redmine-1.2.1-1/ctlscript.sh start|stop|restart
[/bash]
如果成功会显示类似如下信息

111026 16:53:14 mysqld_safe Logging to ‘/opt/redmine-1.2.1-1/mysql/data/mysqld.log’.
111026 16:53:14 mysqld_safe Starting mysqld.bin daemon with databases from /opt/redmine-1.2.1-1/mysql/data
/opt/redmine-1.2.1-1/mysql/scripts/ctl.sh : mysql  started at port 3316
starting port 3001
starting port 3002
Syntax OK
/opt/redmine-1.2.1-1/apache2/scripts/ctl.sh : httpd started at port 80

 

数据库可以直接从已经配置好的windows mysql到出成utf8,再在linux下用命令导入即可。

如果不知道Bitnami Stack安装后的mysql用户名密码,可以到apps/FaceProject/config/database.yml中查看。

 

如果安装时没有选择配置邮件服务器或是需要修改邮件服务器配置,可以直接在

apps/FaceProject/config/email.yml中配置

内容大致如:
[bash]
production:
delivery_method: :smtp
smtp_settings:
address: smtp.yeah.net
port: 25
domain: yeah.net
authentication: :login
user_name: xxx

password: xxx

development:
delivery_method: :smtp
smtp_settings:
address: smtp.yeah.net
port: 25
domain: yeah.net
authentication: :login
user_name: xxx

password: xxx
[/bash]

SQLServer2005/2008导出表结构到excel

1.选中想要导出的数据库;

2.执行下面的代码
[sql]
SELECT
表名=case when a.colorder=1 then d.name else ” end,
表说明=case when a.colorder=1 then isnull(f.value,”) else ” end,
字段序号=a.colorder,
字段名=a.name,
标识=case when COLUMNPROPERTY( a.id,a.name,’IsIdentity’)=1 then ‘√’else ” end,
主键=case when exists(SELECT 1 FROM sysobjects where xtype=’PK’ and name in (
SELECT name FROM sysindexes WHERE indid in(
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
))) then ‘√’ else ” end,
类型=b.name,
占用字节数=a.length,
长度=COLUMNPROPERTY(a.id,a.name,’PRECISION’),
小数位数=isnull(COLUMNPROPERTY(a.id,a.name,’Scale’),0),
允许空=case when a.isnullable=1 then ‘√’else ” end,
默认值=isnull(e.text,”),
字段说明=isnull(g.[value],”)
FROM syscolumns a
left join systypes b on a.xtype=b.xusertype
inner join sysobjects d on a.id=d.id and d.xtype=’U’ and d.name&lt;&gt;’dtproperties’
left join syscomments e on a.cdefault=e.id
left join sys.extended_properties g on a.id=g.major_id and a.colid=g.minor_id
left join sys.extended_properties f on d.id=f.major_id and f.minor_id =0
–where d.name=’要查询的表’ –如果只查询指定表,加上此条件
order by a.id,a.colorder
[/sql]
3.将查询结果导出到.csv文件

另外,sqlserver 2000

[sql]
SELECT
表名=case when a.colorder=1 then d.name else ” end,
表说明=case when a.colorder=1 then isnull(f.value,”) else ” end,
字段序号=a.colorder,
字段名=a.name,
标识=case when COLUMNPROPERTY( a.id,a.name,’IsIdentity’)=1 then ‘√’else ” end,
主键=case when exists(SELECT 1 FROM sysobjects where xtype=’PK’ and name in (
SELECT name FROM sysindexes WHERE indid in(
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
))) then ‘√’ else ” end,
类型=b.name,
占用字节数=a.length,
长度=COLUMNPROPERTY(a.id,a.name,’PRECISION’),
小数位数=isnull(COLUMNPROPERTY(a.id,a.name,’Scale’),0),
允许空=case when a.isnullable=1 then ‘√’else ” end,
默认值=isnull(e.text,”),
字段说明=isnull(g.[value],”)
FROM syscolumns a
left join systypes b on a.xtype=b.xusertype
inner join sysobjects d on a.id=d.id and d.xtype=’U’ and d.name&lt;&gt;’dtproperties’
left join syscomments e on a.cdefault=e.id
left join sysproperties g on a.id=g.id and a.colid=g.smallid
left join sysproperties f on d.id=f.id and f.smallid=0
–where d.name=’要查询的表’ –如果只查询指定表,加上此条件
order by a.id,a.colorder
[/sql]