我正在创建一个应用程序,客户端需要在同一网络上找到服务器。
服务器:
public static void StartListening(Int32 port) {
TcpListener server = new TcpListener(IP.GetCurrentIP(), port);
server.Start();
Thread t = new Thread(new ThreadStart(() =>
{
while (true)
{
// wait for connection
TcpClient client = server.AcceptTcpClient();
if (stopListening)
{
break;
}
}
}));
t.IsBackground = true;
t.Start();
}
假设服务器正在侦听端口12345
然后是客户:
创建所有可用IP地址的列表。服务器IP地址可能与客户端的ip相关,如果它们位于同一本地网络上,因此我将列表构造为:
192.168.5.0
192.168.5.1
192.168.5.2
192.168.5.3
.....etc
.....
192.168.0.88
192.168.1.88
192.168.2.88
192.168.3.88
...etc
192.0.5.88
192.1.5.88
192.2.5.88
192.3.5.88
192.4.5.88
..... etc
0.168.5.88
1.168.5.88
2.168.5.88
3.168.5.88
4.168.5.88
.... etc
然后我尝试连接每个可能的ip和端口12345.如果一个连接成功,那意味着我找到了服务器的地址。
现在我以两种方式做到了这一点。我只知道关于线程的基础知识,我不知道这是否有危险,但它的工作速度非常快。
// first way
foreach (var ip in ListOfIps)
{
new Thread(new ThreadStart(() =>
{
TryConnect(ip);
})).Start();
}
我相信它的第二种方式更安全,但需要更多时间:
// second way
foreach (var ip in ListOfIps)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(TryConnect), ip);
}
我必须调用TryConnect方法大约1000次,每次大约需要2秒(我将连接超时设置为2秒)。什么是最有效和最安全的方式来调用它1000次?
以下是使用不同技术的结果:
1)使用threadpool
..
..
var now = DateTime.Now;
foreach (var item in allIps)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), item);
}
ThreadPool.QueueUserWorkItem(new WaitCallback(PrintTimeDifference), now);
}
static void PrintTimeDifference(object startTime)
{
Console.WriteLine("------------------Done!----------------------");
var s = (DateTime)startTime;
Console.WriteLine((DateTime.Now-s).Seconds);
}
需要37秒才能完成
2)使用主题:
..
..
var now = DateTime.Now;
foreach (var item in allIps)
{
new Thread(new ThreadStart(() =>
{
DoWork(item);
})).Start();
}
ThreadPool.QueueUserWorkItem(new WaitCallback(PrintTimeDifference), now);
需要12秒才能完成
3)使用任务:
..
..
var now = DateTime.Now;
foreach (var item in allIps)
{
var t = Task.Factory.StartNew(() =>
DoWork(item)
);
}
ThreadPool.QueueUserWorkItem(new WaitCallback(PrintTimeDifference), now);
}
static void PrintTimeDifference(object startTime)
{
Console.WriteLine("------------------Done!----------------------");
var s = (DateTime)startTime;
Console.WriteLine((DateTime.Now-s).Seconds);
}
花了8秒!!
答案 0 :(得分:3)
在这种情况下,我更喜欢使用ThreadPool-Threads的解决方案,因为创建1000个线程是一个繁重的操作(当你想到每个线程得到的内存时)。
但是从.NET 4开始,还有另一个类Task
的解决方案。
任务是可以并行执行的工作负载。您可以像这样定义和运行它们:
var t = Task.Factory.StartNew(() => DoAction());
您不必关心使用的线程数,因为运行时环境会处理这些线程。因此,如果您有可能将工作负载拆分为可以并行执行的较小包,我将使用Tasks来完成工作。
答案 1 :(得分:2)
这两种方法都存在创建太多线程的风险。
线程在创建和内存消耗方面的代价很高。
看起来你的第二种方法,使用ThreadPool,应该更好。由于长时间超时(2秒),它仍会创建许多线程,但远低于1000.
更好的方法(需要Fx 4)是使用Parallel.ForEach(...)
。但这也可能需要一些调整。
一个非常好的解决方案是使用广播(UDP)协议来发现服务。
答案 2 :(得分:1)
这种方法有利有弊:
每个连接使用一个单独的线程(理论上)允许您并行进行所有连接,因为这是阻塞I / O操作,所有线程将被挂起,直到相应的连接成功。但是,创建1000个线程对系统来说有点过分。
使用线程池为您提供了重用线程的好处,但一次只能激活有限数量的连接任务。例如,如果线程池有4个线程,那么将尝试4个连接,然后是另外4个连接,依此类推。资源很少但可能需要很长时间,因为正如您所说,单个连接需要大约2秒钟。
所以我建议权衡:创建一个包含大约50个线程的线程池(使用SetMaxThreads
方法)并排队所有连接。这样,它将比1000个线程更轻的资源,并且仍然可以合理地快速处理连接。
答案 3 :(得分:1)
现在我做了自己的基准测试。
以下是代码:
class Program {
private static long parallelIterations = 100;
private static long taskIterations = 100000000;
static void Main(string[] args) {
Console.WriteLine("Parallel Iterations: {0:n0}", parallelIterations);
Console.WriteLine("Task Iterations: {0:n0}", taskIterations);
Analyse("Simple Threads", ExecuteWorkWithSimpleThreads);
Analyse("ThreadPool Threads", ExecuteWorkWithThreadPoolThreads);
Analyse("Tasks", ExecuteWorkWithTasks);
Analyse("Parallel For", ExecuteWorkWithParallelFor);
Analyse("Async Delegates", ExecuteWorkWithAsyncDelegates);
}
private static void Analyse(string name, Action action) {
Stopwatch watch = new Stopwatch();
watch.Start();
action();
watch.Stop();
Console.WriteLine("{0}: {1} seconds", name.PadRight(20), watch.Elapsed.TotalSeconds);
}
private static void ExecuteWorkWithSimpleThreads() {
Thread[] threads = new Thread[parallelIterations];
for (long i = 0; i < parallelIterations; i++) {
threads[i] = new Thread(DoWork);
threads[i].Start();
}
for (long i = 0; i < parallelIterations; i++) {
threads[i].Join();
}
}
private static void ExecuteWorkWithThreadPoolThreads() {
object locker = new object();
EventWaitHandle waitHandle = new ManualResetEvent(false);
int finished = 0;
for (long i = 0; i < parallelIterations; i++) {
ThreadPool.QueueUserWorkItem((threadContext) => {
DoWork();
lock (locker) {
finished++;
if (finished == parallelIterations)
waitHandle.Set();
}
});
}
waitHandle.WaitOne();
}
private static void ExecuteWorkWithTasks() {
Task[] tasks = new Task[parallelIterations];
for (long i = 0; i < parallelIterations; i++) {
tasks[i] = Task.Factory.StartNew(DoWork);
}
Task.WaitAll(tasks);
}
private static void ExecuteWorkWithParallelFor() {
Parallel.For(0, parallelIterations, (n) => DoWork());
}
private static void ExecuteWorkWithAsyncDelegates() {
Action[] actions = new Action[parallelIterations];
IAsyncResult[] results = new IAsyncResult[parallelIterations];
for (long i = 0; i < parallelIterations; i++) {
actions[i] = DoWork;
results[i] = actions[i].BeginInvoke((result) => { }, null);
}
for (long i = 0; i < parallelIterations; i++) {
results[i].AsyncWaitHandle.WaitOne();
results[i].AsyncWaitHandle.Close();
}
}
private static void DoWork() {
//Thread.Sleep(TimeSpan.FromMilliseconds(taskDuration));
for (long i = 0; i < taskIterations; i++ ) { }
}
}
以下是不同设置的结果:
并行迭代:100.000
任务迭代:100
简单线程:13,4589412秒
ThreadPool线程:0,0682997秒
任务:0,1327014秒
平行于:0,0066053秒
异步代表:2,3844015秒
并行迭代:100
任务迭代:100.000.000
简单线程:5,6415113秒
ThreadPool线程:5,5798242秒
任务:5,6261562秒
平行于:5,8721274秒
异步代表:5,6041608秒
正如你所看到的那样,简单的线程在它们太多时效率不高。 但是当使用它们中的一些时,它们非常有效,因为几乎没有开销(例如同步)。