您的位置:首页 > 文旅 > 旅游 > 外包公司排行_全国工商信息查询_怎么样把自己的产品网上推广_chatgpt入口

外包公司排行_全国工商信息查询_怎么样把自己的产品网上推广_chatgpt入口

2025/1/9 10:22:30 来源:https://blog.csdn.net/weixin_50497980/article/details/144950976  浏览:    关键词:外包公司排行_全国工商信息查询_怎么样把自己的产品网上推广_chatgpt入口
外包公司排行_全国工商信息查询_怎么样把自己的产品网上推广_chatgpt入口

C#实现异步多线程的方式有多种,以下总结的是ThreadPool的用法。

线程池的特点

线程池受CLR管理,线程的生命周期,任务调度等细节都不需要我们操心了,我们只需要专注于任务实现,使用ThreadPool提供的静态方法把我们的任务添加到任务队列中,剩下的请放心交给CLR。

  • 线程重用:和其它池化资源有着共同特点,当创建某个对象,创建和销毁代价太高得情况,而且这个对象又可以反复利用,往往我们可以准备一个容器,这个容器可以保存一批这样的对象,如果需要使用这个对象,不需要创建,可以去池中去拿,拿到就用,用完再放回池中,这样就减少了创建和销毁的开销,性能可以得到提升。
  • ThreadPool使用简单,没有直接操作线程的API,例如暂停,恢复,销毁线程,设置前台后台线程(默认都是后台线程),设置优先级等。
  • 有线程数限制,无法无限使用。

环境准备

Visual Studio 创建测试项目,我使用的是Windows Forms App(.NET Framework)模板创建。简单的添加几个测试按钮,在Click事件中进行简单测试。并且准备一段模拟非常耗时操作的代码,例如:

private void DoSomethingLong(string name)
{Console.WriteLine($"********** DoSomethingLong Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");long lResult = 0;//2000000000? 能看出明显延时就好for (int i = 0; i < 2000000000; i++){lResult += i;}Console.WriteLine($"********** DoSomethingLong   End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}

认识下ThreadPool

两种常用启动方式

  • WaitCallback:本质是一个委托,没有返回值,有一个object类型的参数。如果任务不需要传入参数可以使用:public static bool QueueUserWorkItem(WaitCallback callBack)
  • 如果需要传入参数可以使用另外一个重载方法,传入的object类型参数会自动传入到WaitCallback委托中的object参数中:public static bool QueueUserWorkItem(WaitCallback callBack, object state)
private void BtnThreadPool_Click(object sender, EventArgs e)
{Console.WriteLine("");Console.WriteLine($"********** BtnThreadPool_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");WaitCallback waitCallbackWithoutParam = state => Console.WriteLine($"waitCallbackWithoutParam {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");ThreadPool.QueueUserWorkItem(waitCallbackWithoutParam);WaitCallback waitCallbackWithParam = state => Console.WriteLine($"waitCallbackWithParam state={state} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");ThreadPool.QueueUserWorkItem(waitCallbackWithParam, "ParameterizedThreadPool");Console.WriteLine($"********** BtnThreadPool_Click   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}

输出结果:
在这里插入图片描述

设置&获取线程数

  • SetMaxThreads:设置最大线程数:
  • SetMinThreads:设置最小线程数:
  • GetMaxThreads:获取最大线程数:
  • GetMinThreads:获取最小线程数:
private void BtnThreadPoolInfo_Click(object sender, EventArgs e)
{{//默认线程数ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);Console.WriteLine($"默认线程数:workerThreadsMax={workerThreadsMax} workerThreadsMin={workerThreadsMin}");}{//设置线程数ThreadPool.SetMaxThreads(16, 16);    //设置最大线程数ThreadPool.SetMinThreads(12, 12);    //设置最小线程数ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);Console.WriteLine($"设置线程数:workerThreadsMax={workerThreadsMax} workerThreadsMin={workerThreadsMin}");}
}

输出结果:
在这里插入图片描述
我用的电脑是6核12线程的,这个分配是不是很合理?所以CLR是很“聪明”的。如果没有什么极端的需求通常我们不需要自作聪明的去设置他们;

线程等待

ThreadPool没有提供等待线程完成的API,可以使用ManualResetEvent:

private void BtnThreadPoolWait_Click(object sender, EventArgs e)
{Console.WriteLine("");Console.WriteLine($"********** BtnThreadPoolWait_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");ManualResetEvent manualResetEvent = new ManualResetEvent(false);ThreadPool.QueueUserWorkItem(state =>{this.DoSomethingLong("BtnThreadPoolWait_Click");manualResetEvent.Set();});for (int i = 0; i < 1000000000; i++){}Console.WriteLine($"********** 主线程可以先干点别的 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");manualResetEvent.WaitOne();Console.WriteLine($"********** BtnThreadPoolWait_Click   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}

输出结果:
在这里插入图片描述

为什么死锁了?

看下下面使用线程池发生死锁的例子:

private void BtnThreadPoolDeadLock_Click(object sender, EventArgs e)
{Console.WriteLine("");Console.WriteLine($"********** BtnThreadPoolDeadLock_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");ThreadPool.SetMaxThreads(16, 16);ManualResetEvent manualResetEvent = new ManualResetEvent(false);for (int i = 0; i < 20; i++){int k = i;ThreadPool.QueueUserWorkItem(state =>{Console.WriteLine($"k={k} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");if (k < 18){manualResetEvent.WaitOne();}else{manualResetEvent.Set();}});}if (manualResetEvent.WaitOne()){Console.WriteLine("没有死锁");}Console.WriteLine($"********** BtnThreadPoolDeadLock_Click   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}

输出结果:
在这里插入图片描述

  • 程序开始最大线程数被设置为16,线程池中所有线程都在阻塞等待信号,导致发生死锁,程序已经卡死,因为没有线程可用了,发送信号的任务无法执行。
  • 一般不要阻塞线程池的线程。
  • 不要把最大线程数设置的很小,使用默认即可。

如何回调

上一篇文章我们讲委托的异步调用时,提到BeginInvoke可以传入回调,在线程执行完后自动执行这个回调,可是ThreadPool并没有提供传入回调的API,我们可以自己动手去封装一个,只要启动线程时先完成线程任务委托的调用,再执行回调就可以了,例如:

private void ThreadPoolWithCallback(Action act, Action callback)
{ThreadPool.QueueUserWorkItem(state =>{act.Invoke();callback.Invoke();});
}private void BtnThreadPoolWithCallback_Click(object sender, EventArgs e)
{this.ThreadPoolWithCallback(() =>{Console.WriteLine($"这里是Action Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");for (int i = 0; i < 2000000000; i++){}Console.WriteLine($"这里是Action   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");}, () =>{Console.WriteLine($"这里是Callback Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");for (int i = 0; i < 2000000000; i++){}Console.WriteLine($"这里是Callback   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");});
}

输出结果:
在这里插入图片描述

如何获取返回值

上一篇文章我们讲委托的异步调用时,提到EndInvoke可以等待线程结束并获取返回值,可是ThreadPool没有提供获取线程返回值的API,传入的委托都是无返回值的,我们可以自己封装,先看下有问题的封装:

private T ThreadPoolWithReturn<T>(Func<T> func)
{T t = default(T);ManualResetEvent manualResetEvent = new ManualResetEvent(false);ThreadPool.QueueUserWorkItem(state =>{t = func.Invoke();manualResetEvent.Set();});manualResetEvent.WaitOne();return t;
}private void BtnThreadPoolWithReturn_Click(object sender, EventArgs e)
{int result = this.ThreadPoolWithReturn<int>(() =>{Console.WriteLine($"这里是func Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");for (int i = 0; i < 2000000000; i++){}Console.WriteLine($"这里是func   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");return DateTime.Now.Millisecond;});Console.WriteLine($"result={result} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");
}

输出结果:
在这里插入图片描述
应该看出来虽然拿到了结果,但仔细看这个异步是“假异步”,线程刚启动就开始阻塞,等待线程拿到结果,这不和同步一样了吗?这里好像有点矛盾,又要结果,又不想阻塞,是不可能的,要结果就要等计算完成,但这个等待的时机我们可以好好斟酌一下,没必要子线程刚启动,主线程就阻塞等待,我们可以在子线程启动后,主线程先干点别的,等主线程真正要拿结果的时候再等待。那要如何实现?可以在子线程启动后返回一个委托,等需要拿结果的时候我们再调用这个委托。

private Func<T> ThreadPoolWithReturn<T>(Func<T> func)
{T t = default(T);ManualResetEvent manualResetEvent = new ManualResetEvent(false);ThreadPool.QueueUserWorkItem(state =>{t = func.Invoke();manualResetEvent.Set();});//返回委托在这里可以立即结束不需要阻塞,只有要结果的时候才会等待完成//因为这个委托中持有上下文环境比如 manualResetEvent 比如t,所以外部调用委托的时候可以拿到这些对象return () =>{manualResetEvent.WaitOne();return t;};
}private void BtnThreadPoolWithReturn_Click(object sender, EventArgs e)
{Console.WriteLine("");Console.WriteLine($"********** BtnThreadPoolWithReturn_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");Func<int> func = this.ThreadPoolWithReturn<int>(() =>{Console.WriteLine($"这里是func Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");for (int i = 0; i < 2000000000; i++){}Console.WriteLine($"这里是func   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");return DateTime.Now.Millisecond;});//假如拿结果前做了一个耗时的计算for (int i = 0; i < 1000000000; i++){}Console.WriteLine($"主线程拿结果前可以在这里干点别的... {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");int result = func.Invoke();Console.WriteLine($"result={result} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy - MM - dd HH: mm:ss.fff")}");Console.WriteLine($"********** BtnThreadPoolWithReturn_Click   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}

输出结果:
在这里插入图片描述

  • 返回委托并不会阻塞,只有在调用这个委托才会真正等待结果。
  • 拿结果前主线程可以和子线程可以并发运行。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com