我是C#的新手,我想使用线程编写一个聊天程序,其中每一侧有两个线程(一个用于监听,另一个用于发送消息)。
但是,线程无法使用组件(即Textbox),因为另一个线程已经捕获了它。
这是我的代码:
服务器端:
private TcpClient clientSocket;
private void btnStartListening_Click(object sender, EventArgs e)
{
TcpListener serverSocket = new TcpListener(int.Parse(txtPort.Text));
int requestCount = 0;
clientSocket = default(TcpClient);
serverSocket.Start();
txtReceivedMessages.Text +=" >> Server Started\n";
clientSocket = serverSocket.AcceptTcpClient();
txtReceivedMessages.Text += " >> Accept connection from client\n";
requestCount = 0;
Thread t = new Thread(listeningThread);
t.Start();
clientSocket.Close();
serverSocket.Stop();
}
private void listeningThread()
{
while (true)
{
try
{
NetworkStream networkStream = clientSocket.GetStream();
byte[] bytesFrom = new byte[10025];
networkStream.Read(bytesFrom, 0, (int)clientSocket.ReceiveBufferSize);
string dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom);
dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$"));
string serverResponse = "Last Message from client" + dataFromClient;
networkStream.Flush();
txtReceivedMessages.Text += dataFromClient;
txtReceivedMessages.Update();
}
catch (Exception ex)
{
txtReceivedMessages.Text += ex.ToString() + "\n";
}
}
}
private void btnSend_Click(object sender, EventArgs e)
{
NetworkStream networkStream = clientSocket.GetStream();
Byte[] data = Encoding.ASCII.GetBytes(txtSendMessage.Text);
networkStream.Write(data, 0, data.Length);
}
客户方:
private NetworkStream serverStream;
private void btnSend_Click(object sender, EventArgs e)
{
serverStream = clientSocket.GetStream();
byte[] outStream = System.Text.Encoding.ASCII.GetBytes(txtMsgToSend.Text + "$");
serverStream.Write(outStream, 0, outStream.Length);
serverStream.Flush();
}
private void listeningThread()
{
byte[] inStream = new byte[10025];
serverStream.Read(inStream, 0, (int)clientSocket.ReceiveBufferSize);
string returndata = System.Text.Encoding.ASCII.GetString(inStream);
txtReceivedMsgs.Text += ">>" + returndata;
txtMsgToSend.Text = "";
txtMsgToSend.Focus();
}
private void btnSetConfiguration_Click(object sender, EventArgs e)
{
txtReceivedMsgs.Text += "Client Started";
clientSocket.Connect(txtIP.Text, int.Parse(txtPort.Text));
lblStatus.Text = "Client Socket Program - Server Connected ...";
}
答案 0 :(得分:0)
如果要从另一个线程更新表单控件,则需要使用Invoke()方法。
从SO上的这篇文章中查看更多内容 How to update the GUI from another thread in C#?
修改:来自关联帖子的内容
对于.NET 2.0,这里有一些我编写的代码,它可以完全满足您的需求,适用于Control上的任何属性:
private delegate void SetControlPropertyThreadSafeDelegate(
Control control,
string propertyName,
object propertyValue);
public static void SetControlPropertyThreadSafe(
Control control,
string propertyName,
object propertyValue)
{
if (control.InvokeRequired)
{
control.Invoke(new SetControlPropertyThreadSafeDelegate
(SetControlPropertyThreadSafe),
new object[] { control, propertyName, propertyValue });
}
else
{
control.GetType().InvokeMember(
propertyName,
BindingFlags.SetProperty,
null,
control,
new object[] { propertyValue });
}
}
这样称呼:
// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);
如果您使用的是.NET 3.0或更高版本,则可以将上述方法重写为Control类的扩展方法,这样可以简化对以下内容的调用:
myLabel.SetPropertyThreadSafe("Text", status);
更新05/10/2010:
对于.NET 3.0,您应该使用以下代码:
private delegate void SetPropertyThreadSafeDelegate<TResult>(
Control @this,
Expression<Func<TResult>> property,
TResult value);
public static void SetPropertyThreadSafe<TResult>(
this Control @this,
Expression<Func<TResult>> property,
TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member
as PropertyInfo;
if (propertyInfo == null ||
!@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
@this.GetType().GetProperty(
propertyInfo.Name,
propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (@this.InvokeRequired)
{
@this.Invoke(new SetPropertyThreadSafeDelegate<TResult>
(SetPropertyThreadSafe),
new object[] { @this, property, value });
}
else
{
@this.GetType().InvokeMember(
propertyInfo.Name,
BindingFlags.SetProperty,
null,
@this,
new object[] { value });
}
}
使用LINQ和lambda表达式来允许更清晰,更简单和更安全的语法:
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile
现在不仅在编译时检查属性名称,属性的类型也是如此,因此不可能(例如)将字符串值赋给布尔属性,因此导致运行时异常。
不幸的是,这并不能阻止任何人做一些愚蠢的事情,例如传递另一个Control的属性和值,所以以下内容将很乐意编译:
myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
因此,我添加了运行时检查,以确保传入的属性确实属于调用该方法的Control。不完美,但仍然比.NET 2.0版本好很多。
如果有人对如何为编译时安全性改进此代码有任何进一步的建议,请发表评论!