对delegate进行扩展 打造通用的"计时完成"方法
连接数据库是程序员经常要做的事情, 通常情况下, 需要连接SQL Server中哪个数据库是已知的, 用户在输入SQL Server的服务器IP、用户名、密码和数据库名字后, 如果所有信息输入正确, 就可以建立连接了.
让用户尽量少打字
每次让用户输入这么多信息的确很糟糕, 可以改进一下设计: 服务器IP和用户名可以存放在配置文件里面, 初始化的时候默认加载到相应的文本框中; 从安全角度考虑, 密码必须经过用户手动输入; 而数据库名字则没必要让用户输入, 有了服务器IP、用户名、密码后可以尝试连接SQL Server, 连接SQL Server成功后, 把数据库中所有的数据库名加载到ComboBox让用户选择连接哪个数据库.
密码不正确
密码正确
如何实现
在后台代码中定义一个计时器, 设置它的Interval为1000毫秒, 用户输入密码时让定时器重新计时, 也就是说用户输入密码后, 如果在1秒钟内用户没有继续输入密码, 则会触发计时器的Elapsed事件, 这时程序尝试能不能连上SQL Server.
//用户停止输入密码1秒后自动尝试连接 private System.Timers.Timer m_timer=new System.Timers.Timer(1000); private void txtPassword_PasswordChanged(object sender, RoutedEventArgs e) { m_timer.Stop(); m_timer.Start(); //清空数据库的选择列表 cbbDatabase.Items.Clear(); }
在计时器的Elapsed事件函数中, 程序尝试连接SQL Server, 但是如下写法会出现问题.
private void timer_Elapsed(object sender, ElapsedEventArgs e) { Dispatcher.BeginInvoke((Action)delegate() { var conStr = String.Format("Data Source={0};Integrated Security=False;User ID={1};Password={2};", txtServer.Text, txtUser.Text, txtPassword.Password); using (var con = new SqlConnection(conStr)) { try { con.Open(); } catch { //TODO: 提示连接数据库失败 return; } //TODO:提示连接数据库成功 var sql = "select name from sys.databases"; var cmd = new SqlCommand(sql, con); IAsyncResult asyncResult = cmd.BeginExecuteReader(); SqlDataReader reader = cmd.EndExecuteReader(asyncResult); if (!reader.HasRows) { //TODO: 提示SQL Server中不存在数据库 return; } //TODO: 在这里释放Timer while (reader.Read()) { cbbDatabase.Items.Add(reader[0].ToString()); } } }); }
如果数据库的IP是错误的, con.Open()则要花费约30秒左右才会抛出异常, 这段时间UI线程会一直卡死, 经过测试, 30秒后才抛异常与数据库连接字符串的Connect Timeout属性没有关系.
可以写一个SqlConnection的扩展方法, 在这个方法里面用一个码表对执行con.Open()的时间计时, 用一个bool型变量标识有没有成功的con.Open(), 如果在规定的时间内没有成功连上SQL Server则抛出一个异常.
扩展SqlConnection
public static class SqlExtension { public static void TryOpen(this SqlConnection connection, int millisecondTimeout) { Stopwatch sw = new Stopwatch(); bool succeed = false; Thread t = new Thread(() => { try { sw.Start(); connection.Open(); //打开连接后设置succeed为true succeed = true; } catch { } }); t.IsBackground = true; t.Start(); t.Join(millisecondTimeout); t.Abort(); //如果没有成功, 则抛出一个异常 if (!succeed) throw new Exception(); } }
这样在原来的代码中调用con.TryOpen()就可以解决问题.
扩展delegate
其实扩展方法的第一个参数并不是一定要是一个class类, 其实delegate也是可以的, delegate其实在编译之后就是一个类, 如下改写刚刚的SqlConnection扩展方法, 就可以写一个通用的尝试操作的扩展方法.
public static class ActionExtension { public static void Invoke(this Action action, int millisecondTimeout) { Stopwatch sw = new Stopwatch(); bool succeed = false; Thread t = new Thread(() => { try { sw.Start(); action(); succeed = true; } catch { } }); t.IsBackground = true; t.Start(); t.Join(millisecondTimeout); if (!succeed) throw new Exception(); } }
这样写, 代码则更为通用一些, 以后一些耗时的操作(当然要满足Action的签名)都可以调用这个方法, 在本例中, 可以如下调用:
((Action)con.Open).Invoke(2000);
作者:Create Chen
出处:http://technology.cnblogs.com
说明:文章为作者平时里的思考和练习,可能有不当之处,请博客园的园友们多提宝贵意见。
本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。