在C#中,经常用到这样一个场景,Windows Form程序启动一个工作者线程执行一部分工作,这样做是为了避免速度慢的工作如果直接调用会使得主Form停止响应一段时间。
既然启动了线程,就避免不了线程之间数据传递的事情,相信你有很多种办法能解决,总之注意同步和互斥操作就好。我想说的是,工作线程处理中可能想操作某个主线程的Windows Form的Control,比如按钮,ListView等等更新工作状态之类,直接控制是不行的,不能够跨线程操作另一个线程创建的Windows Form控件。要使用委托去调用。
[code=’c#’]
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace JPGCompact
{
public partial class MainForm : Form
{
// 定义委托
private delegate void DelegateWriteResult(string file, bool result);

// 与定义的委托签名相同的函数,操作主线程控件
private void WriteResult(string fileName, bool result)
{
if (result)
{
ListViewItem thisListItem = new ListViewItem();
thisListItem.ForeColor = Color.White;
thisListItem.BackColor = Color.DarkGreen;
thisListItem.SubItems[0].Text = fileName;
thisListItem.SubItems.Add(“成功”);
lvResultList.Items.Add(thisListItem);
}
else
{
ListViewItem thisListItem = new ListViewItem();
thisListItem.ForeColor = Color.White;
thisListItem.BackColor = Color.Red;
thisListItem.SubItems[0].Text = fileName;
thisListItem.SubItems.Add(“失败”);
lvResultList.Items.Add(thisListItem);
}
}

// 启动线程
private void btnStart_Click(object sender, EventArgs e)
{
Thread workThread = new Thread(new ThreadStart(CompressAll));
// 设置为背景线程,主线程一旦推出,该线程也不等待而立即结束
workThread.IsBackground = true;
workThread.Start();
}

// 线程执行函数
private void CompressAll()
{
// 判断是否需要Invoke,多线程时需要
if (this.InvokeRequired)
{
// 通过委托调用写主线程控件的程序,传递参数放在object数组中
this.Invoke(new DelegateWriteResult(WriteResult),
new object[] { item, true });
}
else
{
// 如果不需要委托调用,则直接调用
this.WriteResult(item, true);
}
}
}
}
[/code]

不同的操作系统,桌面的路径不尽相同,而且随着用户安装位置的不同也不同。
C#可以从Windows注册表读取得到用户的特殊文件夹(桌面、收藏夹等等)的位置。
代码如下:
[code=’c#’]
using Microsoft.Win32;
namespace JPGCompact
{
public partial class MainForm : Form
{
private void Test()
{
RegistryKey folders;
folders = OpenRegistryPath(Registry.CurrentUser, @”\software\microsoft\windows\currentversion\explorer\shell folders”);
// Windows用户桌面路径
string desktopPath = folders.GetValue(“Desktop”).ToString();
// Windows用户字体目录路径
string fontsPath = folders.GetValue(“Fonts”).ToString();
// Windows用户网络邻居路径
string nethoodPath = folders.GetValue(“Nethood”).ToString();
// Windows用户我的文档路径
string personalPath = folders.GetValue(“Personal”).ToString();
// Windows用户开始菜单程序路径
string programsPath = folders.GetValue(“Programs”).ToString();
// Windows用户存放用户最近访问文档快捷方式的目录路径
string recentPath = folders.GetValue(“Recent”).ToString();
// Windows用户发送到目录路径
string sendtoPath = folders.GetValue(“Sendto”).ToString();
// Windows用户开始菜单目录路径
string startmenuPath = folders.GetValue(“Startmenu”).ToString();
// Windows用户开始菜单启动项目录路径
string startupPath = folders.GetValue(“Startup”).ToString();
// Windows用户收藏夹目录路径
string favoritesPath = folders.GetValue(“Favorites”).ToString();
// Windows用户网页历史目录路径
string historyPath = folders.GetValue(“History”).ToString();
// Windows用户Cookies目录路径
string cookiesPath = folders.GetValue(“Cookies”).ToString();
// Windows用户Cache目录路径
string cachePath = folders.GetValue(“Cache”).ToString();
// Windows用户应用程式数据目录路径
string appdataPath = folders.GetValue(“Appdata”).ToString();
// Windows用户打印目录路径
string printhoodPath = folders.GetValue(“Printhood”).ToString();
}

private RegistryKey OpenRegistryPath(RegistryKey root, string s)
{
s = s.Remove(0, 1) + @”\”;
while (s.IndexOf(@”\”) != -1)
{
root = root.OpenSubKey(s.Substring(0, s.IndexOf(@”\”)));
s = s.Remove(0, s.IndexOf(@”\”) + 1);
}
return root;
}
}
}
[/code]

Effictive C#, 50 Specific Ways to Improve Your C#一书提到,运行时常量readonly比编译时常量const更好。不是因为运行时常量readonly会比较快,而是因为这样的常量是运行时查找位置的,而不是编译时直接编译进IL,因此当版本出现交差覆盖的时候不会出错,也许会慢一点点,但是运行时常量readonly总是能够正确运行。
那么,运行时常量readonly到底比编译时常量const慢多少呢?
我写了个测试,框架就不介绍了。

[code=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace CSharpPerformance.TestCase
{
class ReadOnlyVsConst : TestCase
{
private const double ConstValue = 1.23456789;
private readonly double ReadOnlyValue = 1.23456789;

private double DoCalculate(double value, long count)
{
double result = 1;
for (int i = 0; i < count; i++) { result *= value; } return result; } public override void DoTest() { int count = 100000000; name1 = "Const: "; DateTime start = DateTime.Now; DoCalculate(ConstValue, count); DateTime end = DateTime.Now; span1 = end.Subtract(start); name2 = "ReadOnly: "; start = DateTime.Now; DoCalculate(ReadOnlyValue, count); end = DateTime.Now; span2 = end.Subtract(start); } } } [/code] 我执行了10遍,结果是这样的: Debug模式(Optimize code: ON):
[code=’c#’]
Const: 00:00:12.5802579
ReadOnly: 00:00:13.6913690
————————————
Const: 00:00:13.2063205
ReadOnly: 00:00:12.7162715
————————————
Const: 00:00:12.5372536
ReadOnly: 00:00:12.2612260
————————————
Const: 00:00:12.3152314
ReadOnly: 00:00:12.4542453
————————————
Const: 00:00:12.4342433
ReadOnly: 00:00:12.5922591
————————————
Const: 00:00:12.3752374
ReadOnly: 00:00:12.3442343
————————————
Const: 00:00:12.1912190
ReadOnly: 00:00:12.1782177
————————————
Const: 00:00:12.1932192
ReadOnly: 00:00:12.2582257
————————————
Const: 00:00:12.2472246
ReadOnly: 00:00:12.2532252
————————————
Const: 00:00:12.2462245
ReadOnly: 00:00:12.3122311
————————————
[/code]
结果:const VS readonly:6:4

Debug模式(Optimize code: OFF):
[code=’c#’]
Const: 00:00:12.4402439
ReadOnly: 00:00:12.3002299
————————————
Const: 00:00:12.2512250
ReadOnly: 00:00:12.2272226
————————————
Const: 00:00:12.2302229
ReadOnly: 00:00:12.1962195
————————————
Const: 00:00:12.2922291
ReadOnly: 00:00:12.2152214
————————————
Const: 00:00:12.3062305
ReadOnly: 00:00:12.1942193
————————————
Const: 00:00:12.2012200
ReadOnly: 00:00:12.2252224
————————————
Const: 00:00:12.1922191
ReadOnly: 00:00:12.2402239
————————————
Const: 00:00:12.2602259
ReadOnly: 00:00:12.2452244
————————————
Const: 00:00:12.2322231
ReadOnly: 00:00:12.1952194
————————————
Const: 00:00:12.2492248
ReadOnly: 00:00:12.1912190
————————————
[/code]
结果:const VS readonly:2:8,Shocking!

Release模式(Optimize code: ON):
[code=’c#’]
Const: 00:00:08.7728772
ReadOnly: 00:00:08.8708870
————————————
Const: 00:00:08.7698769
ReadOnly: 00:00:08.7088708
————————————
Const: 00:00:08.7148714
ReadOnly: 00:00:08.6878687
————————————
Const: 00:00:08.7258725
ReadOnly: 00:00:08.7758775
————————————
Const: 00:00:08.8258825
ReadOnly: 00:00:08.7748774
————————————
Const: 00:00:08.8948894
ReadOnly: 00:00:08.8378837
————————————
Const: 00:00:08.7198719
ReadOnly: 00:00:08.8348834
————————————
Const: 00:00:08.7338733
ReadOnly: 00:00:08.7398739
————————————
Const: 00:00:08.7138713
ReadOnly: 00:00:08.7688768
————————————
Const: 00:00:08.7948794
ReadOnly: 00:00:08.7698769
————————————
[/code]
结果:const VS readonly:5:5

Release模式(Optimize code: OFF):
[code=’c#’]
Const: 00:00:08.9068906
ReadOnly: 00:00:08.9008900
————————————
Const: 00:00:08.9108910
ReadOnly: 00:00:08.7988798
————————————
Const: 00:00:08.8098809
ReadOnly: 00:00:08.7978797
————————————
Const: 00:00:08.8048804
ReadOnly: 00:00:08.8218821
————————————
Const: 00:00:08.9528952
ReadOnly: 00:00:08.8898889
————————————
Const: 00:00:08.9138913
ReadOnly: 00:00:09.0019001
————————————
Const: 00:00:08.9288928
ReadOnly: 00:00:08.7968796
————————————
Const: 00:00:08.7798779
ReadOnly: 00:00:08.8298829
————————————
Const: 00:00:08.8258825
ReadOnly: 00:00:08.7558755
————————————
Const: 00:00:08.8138813
ReadOnly: 00:00:08.8168816
————————————
[/code]
结果:const VS readonly:4:6

数数看,理论上const应该是绝对快的,其实不然。编译优化关闭的时候,const居然落后于readonly。

SMS.cs,一个订阅者
[sourcecode language=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace EventExample
{
class SMS
{
public void OnNewMail(object sender, NewMailEventArgs e)
{
Console.WriteLine(
“SMS Arrives: [NEW MAIL] {0} to {1}, {2}”,
e.From, e.To, e.Subject);
}
}
}
[/sourcecode]

Phone.cs,另一个订阅者
[sourcecode language=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace EventExample
{
class Phone
{
public void OnNewMail(object sender, NewMailEventArgs e)
{
Console.WriteLine(
“Phone Rings: [NEW MAIL] {0} to {1}, {2}”,
e.From, e.To, e.Subject);
}
}
}
[/sourcecode]

NewMailEventArgs.cs,自定义的事件附加信息类
[sourcecode language=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace EventExample
{
class NewMailEventArgs : EventArgs
{
private readonly string m_from, m_to, m_subject;

public NewMailEventArgs(string from, string to, string subject)
{
this.m_from = from;
this.m_to = to;
this.m_subject = subject;
}

public string From { get { return m_from; } }
public string To { get { return m_to; } }
public string Subject { get { return m_subject; } }
}
}
[/sourcecode]

MailManager.cs,引发事件的类
[sourcecode language=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace EventExample
{
class MailManager
{
public event EventHandler NewMail;

protected virtual void OnNewMail(NewMailEventArgs e)
{
// For thread safe
EventHandler temp = NewMail;

if (temp != null)
{
temp(this, e);
}
}

public void SimulateNewMail(string from, string to, string subject)
{
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
OnNewMail(e);
}
}
}
[/sourcecode]

这里的类使用了一个线程安全的做法,使用了一个OnNewMail虚方法调用事件,在调用之前首先将事件保存到一个temp中,这是因为确保事件不是null,如果直接判断NewMail是不是null,可能会在判断完之后,当时不是null,而紧接着被另一个线程修改成null,从而导致NullReferenceException的情况。

Program.cs
[sourcecode language=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Phone phone = new Phone();
SMS sms = new SMS();
MailManager mailManager = new MailManager();

mailManager.NewMail += phone.OnNewMail;
mailManager.NewMail += sms.OnNewMail;

for (int i = 0; i < 100; i++) { mailManager.SimulateNewMail( "Someone", "Someone else", "Test Mail" + i.ToString()); } } } } [/sourcecode]

C#程序员经常搞不清楚值类型和引用类型的概念和区分,我想说一下。

1. 为什么要有值类型——使用引用类型的代价
引用类型是CLR从托管堆上分配空间而来的,new操作会返回对象的内存地址。在托管堆上分配对象不是没有代价的,包含分配对象时的额外成员(类型对象指针和同步块索引)的初始化操作、对象内部各个字节置零初始化,而且,这个分配操作还可能强制执行一次垃圾收集操作。
当所有类型都是引用类型时,带给托管堆的压力可想而之。并且垃圾收集操作相当耗费时间,这样的开销显然太大。因此需要引入值类型。

2. 值类型的特殊性
从OO上看,在C#中,一切皆是Object,值类型也不例外。但是,值类型在System.Object之上的类型是System.ValueType或者System.Enum(仅枚举类),且,值类型都是sealed类型。
从名称上看,引用类型都是称为类,值类型称为结构或者枚举,SDK中尤其明显。
值类型的实例在线程的堆栈上分配,虽然可以作为字段嵌入一个引用类型中,只不过引用类型的字段存储一个指向托管堆上对象实例的指针,而值类型的字段存储的是该值类型本身。修改的时候在线程堆栈上直接修改。
值类型复制之后是复制内容,修改一个不会影响另一个副本;引用类型复制是复制指针,指向的对象是同一个,因此修改一个会影响另一个副本。

3. 定义自己的值类型
使用class定义一个引用类型,使用struct定义一个值类型。值类型也可以实现一个或者多个接口。
什么时候需要把类型定义成值类型呢?有以下几点:

  • 类型是个基元类型,没有提供可供修改其中字段的方式,即不可变的类型。
  • 不需要继承其他类型。
  • 不会派生出其他类型。

满足以上三点,则满足定义成值类型的充分性,可以考虑定义成值类型。
必要性仍然要考虑类型的大小,因为值类型的复制是传值,而不是简单拷贝指针,大的值类型传递代价比引用类型传递更大。

环境说明
我们项目中使用了一个Web Service,返回List对象,在SOAP中即构成了XML格式字符串返回客户端。
客户端使用WebServiceSoapClient类调用Web Methods。
使用的开发环境是Visual Studio 2008,.NET Framework 3.0,部署在Windows Server 2003上。全部采用英文版。

问题说明
某个返回List的Web Method,一向工作正常。某日在数据库添加了一条记录之后,开始出现Exception。Visual Studio 2008调试报错:
A first chance exception of type ‘System.ServiceModel.CommunicationException’ occurred in mscorlib.dll

然后我们分析认为,是这条记录格式有问题,我们把原记录中“BLACKMAGIC”字符串改成“BLACKMAGIC2”,问题依旧;改成“BLACKMAGI”,问题不见了…
于是第一次就这样了。

过几天又插入一堆记录,CommunicationException又出现了。这一次我试图调整List中包含元素的个数,发现134个可以,而135个不行。猜测Web Service返回的XML字符串有长度限制。本地调试,手动保存了134个和135个返回的XML文件,查看大小,134条记录的文件大小,著名的81,920 bytes给了我重要的猜测证据。

经过一番Google,翻了几个网站,整理得到的解决方案如下:
之前我们使用简单的WebServiceSoapClient构造,会导致81,920 bytes是Web Service XML返回值的最大大小限制:

[code=’c#’]
WebServiceSoapClient proxy = new ITS.ServiceReference.WebServiceSoapClient();
[/code]

使用另一种构造函数并设置返回消息最大值:

[code=’c#’]
System.ServiceModel.BasicHttpBinding binding = new System.ServiceModel.BasicHttpBinding();
binding.MaxReceivedMessageSize = int.MaxValue;
System.ServiceModel.EndpointAddress address = new System.ServiceModel.EndpointAddress(“http://serveraddress/webservicename.asmx”);
WebServiceSoapClient proxy = new ITS.ServiceReference.WebServiceSoapClient(binding, address);
[/code]

则问题解决。原来第一次去掉一个字符,刚刚好够81,920 bytes。

0. 更新日志

  • 2008年07月08日:由于不同版本的Windows对于Balloon气球控件的支持不一样,有时候弹不出气球。全部更换为MessageBox。版本号更新为2.0.0.0版。
  • 2008年06月16日:之前版本事件处理过于细致,以至于一次查询可能弹出很多次。修正限制为只监视刚刚粘贴事件。版本号更新为1.1.0.0版。
  • 2008年04月11日:发现NotifyIcon的ShowBalloonTip呈现的Balloon好像有字数限制,改成超过10条IP会弹框显示。修正一处可能崩溃的bug。
  • 2008年04月11日:发布1.0.0.0版。

1. 功能介绍
Nocoo IP 即查即看工具是Nocoo编写的一款旨在帮助站长了解其访客来源的小工具。以往,站长如果想确定某个IP的地理位置,需要将IP复制下来,使用第三方网站进行查询,非常麻烦,而且不能一次查询多个IP。


运行效果

有了这款工具,站长只需要把文字区域选中,复制,就会在任务栏弹出气球,自动分析剪贴板内所有IP,并将其地址查询结果返回,大大提高站长的管理效率。

2. 系统需求

  • Microsoft .NET Framework 2.0

3. 疑难解答

4. 下载地址