精华内容
下载资源
问答
  • C# winform多线程模板示例,winform多线程例子,C#源码
  • 这是一个C# winform多线程操作示例,可以参考解决界面卡死问题
  • c# winform 多线程 教程

    2010-07-21 13:53:37
    c# winform 多线程 教程c# winform 多线程 教程c# winform 多线程 教程
  • c# winform多线程的小例子,需要的朋友可以参考一下
  • C#WinForm多线程开发.pdf

    2021-09-30 16:15:26
    C#WinForm多线程开发.pdf
  • C# WinForm多线程防止界面假死源码,代码里面包含显示进度的例子,针对多线程使用的一个很好例子。供初级人员参考。
  • C# WinForm多线程

    2012-07-05 20:49:17
    下面我们就把在Windows Form软件中使用Invoke时的多线程要注意的问题给大家做一个介绍。 首先,什么样的操作需要考虑使用多线程?总的一条就是,负责与用户交互的线程(以下简称为UI线程)应该保持顺畅,当UI线程调用...

    http://www.cnblogs.com/joechen/archive/2009/04/30/1446857.html


    下面我们就把在Windows Form软件中使用Invoke时的多线程要注意的问题给大家做一个介绍。

    首先,什么样的操作需要考虑使用多线程?总的一条就是,负责与用户交互的线程(以下简称为UI线程)应该保持顺畅,当UI线程调用的API可能引起阻塞时间超过30毫秒时(比如访问CD-ROM等速度超慢的外设、进行远程调用等等)就应该考虑使用多线程。为什么是30毫秒?30毫秒的概念是人眼可以察觉到的一个迟滞,大约等同于电影里的一帧停留的时间,最长不要超过100毫秒。

    第二,最方便和简单的多线程是使用线程池。通过线程池里的线程运行代码的最简便方法则是使用异步委托调用。注意委托调用通常是同步完成的,请使用BeginInvoke方法,这样就可以把要调用的方法排队到线程池里等候处理,而程序的流程会立刻返回到调用方(此处是UI线程),而调用方因此不会出现阻塞。

    看看下面的例子我们就发现要使用线程池异步执行代码也并非十分复杂,这里我们利用System.Windows.Forms.MethodInvoker委托进行异步调用。注意MethodInvoker委托不接受方法参数,如果需要向异步执行的方法传递参数,请使用其他委托,或者需要自己定义。

    归纳上述方法,对UI线程而言实际上就是:1、发出调用,2、立刻返回,具体运行过程不理了,这样UI线程就不会被阻塞。这种方法很重要,下面我们会深入介绍。除了上面的方法,还有其他使用线程池的方法,当然如果你高兴也可以自己创建线程。

    第三,在Windows Form中使用多线程的,最重要的一条注意事项是,除了创建控件的线程以外,绝对不要在任何其他线程里面调用控件的成员(只有极个别情况例外),也就是说控件属于创建它的线程,不能从其他线程里面访问。这一条适用于所有从System.Windows.Forms.Control派生的控件(因此可以说是几乎所有控件),包括Form控件本身也是。举一反三,我们很容易得出这样的结论,控件的子控件必须由创建控件的线程来创建,比如一个表单上的按钮,比如由创建表单的线程来创建,因此,一个窗口中的所有控件实际上都活在同一个线程之中。在实际编程时,大多数的软件的做法都是让同一线程负责全部的控件,这就是我们所说的UI线程。

    我们要特别提醒大家,很多人刚开始的时候都会使用以上的方法来访问不在同一个线程里的控件(包括笔者本人),而且在1.0版.Net 框架上似乎没有发现问题,但是这根本就是错的,更糟糕的是,程序员在这里不会得到任何错误提示,一开始就上当受骗,之后会莫明其妙地发现其他错误,这就是Windows Form多线程编程的痛苦所在。笔者试过花很多时间来Debug自己写的Splash窗口突然消失的问题,结果还是失败了:笔者在软件的引导过程中,用另外一个线程里创建了一个Splash窗口来显示欢迎信息,然后尝试把主线程里引导的状态直接写入到Splash窗口上的控件中,开始还OK,可是过一会Splash窗口就莫明其妙消失了。

    理解了这一点,我们应该留意到,有时候即使没有用System.Threading.Thread来显式创建一个线程,我们也可能因为使用了异步委托的BeginInvoke方法来隐式创建了线程(从线程池里),在这种线程里也同样不能调用UI线程所创建的控件的成员。

    第四,由于上述限制,我们可能会感到很不方便,的确,当我们利用一个新创建的线程来执行某些花时间的运算时,怎样知道运算进度如何并通过UI反映给用户呢?解决方法很多!比如熟悉多线程编程的用户很快会想到,我们采用一些低级的同步方法,工作者线程把状态保存到一个同步对象中,让UI线程轮询(Polling)该对象并反馈给用户就可以了。不过,这还是挺麻烦的,实际上不用这样做,Control类(及其派生类)对象有一个Invoke方法很特别,这是少数几个不受线程限制的成员之一。我们前面说到,绝对不要在任何其他线程里面调用非本线程创建的控件的成员时,也说了“只有极个别情况例外”,这个Invoke方法就是极个别情况之一----Invoke方法可以从任何线程里面调用。下面我们来讲解Invoke方法。

    Invoke方法的参数很简单,一个委托,一个参数表(可选),而Invoke方法的主要功能就是帮助你在UI线程(即创建控件的线程)上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。注意,使用Invoke方法时,UI线程不能处于阻塞状态。以下MSDN里关于Invoke方法的说明:

    “控件上有四种方法可以安全地从任何线程进行调用:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。对于所有其他方法调用,则应使用调用 (invoke) 方法之一封送对控件的线程的调用。
    委托可以是 EventHandler 的实例,在此情况下,发送方参数将包含此控件,而事件参数将包含 EventArgs.Empty。委托还可以是 MethodInvoker 的实例或采用 void 参数列表的其他任何委托。调用 EventHandler 或 MethodInvoker 委托比调用其他类型的委托速度更快。”

    好了,说完Invoke,顺便说说BeginInvoke,毫无疑问这是Invoke的异步版本(Invoke是同步完成的),不过大家不要和上面的System.Windows.Forms.MethodInvoker委托中的BeginInvoke混淆,两者都是利用不同线程来完成工作,但是控件的BeginInvoke方法总是使用UI线程,而其他的异步委托调用方法则是利用线程池里的线程。相对Invoke而言,使用BeginInvoke稍稍麻烦一点,但还是那句话,异步比同步效果好,尽管复杂些。比如同步方法可能出现这样一种死锁情况:工作者线程通过Invoke同步调用UI线程里的方法时会阻塞,而万一UI线程正在等待工作者线程做某件事时怎么办?因此,能够使用异步方法时应尽量使用异步方法。


    展开全文
  • C# winform 多线程 让子线程也能操作界面的方法 源代码
  • c# Winform 多线程操作

    2020-06-11 16:50:05
    c# Winform 多线程操作 https://www.cnblogs.com/SoftWareIe/p/9650947.html c# winform编程之多线程ui界面资源修改总结篇 https://www.cnblogs.com/gc2013/p/3824761.html

    c# Winform 多线程操作

    https://www.cnblogs.com/SoftWareIe/p/9650947.html

    c# winform编程之多线程ui界面资源修改总结篇

    https://www.cnblogs.com/gc2013/p/3824761.html

    展开全文
  • C# winform 多线程 事例

    2009-07-22 13:27:48
    该事例说明了在 C# winform 多线程 应用与资源互调
  • c#winform多线程

    2013-11-13 11:42:47
    在一个窗口上加载了几个自定义控件,打开这个窗口时出现短暂白屏(卡死似的),为了解决这个问题,我用了多线程,但没有效果.不知道问题出在哪,下面是代码,麻烦给看一下,多谢. private void FormMain_Load(object sender,...
  • C# Winform 多线程下载

    2017-09-14 16:15:50
    根据博文C# 文件上传下载(Excel导入,多线程下载),用Winform多线程下载进行了呈现,原文链接:http://www.cnblogs.com/liudiwei/p/6041641.html
  • 简单winform多线程调用进度条的例子 BeginInvoke
  • C#winform多线程,有6000条数据,分别根据ID查询,把信息导出excel,开100个线程同时查询,线程没走完就导出了,就没有那么多条数据,怎么办?求救 CountdownEvent handler = new CountdownEvent(100); for (int i ...
  • 简单winform多线程调用进度条的例子 BeginInvoke
  • 原文地址:点击打开链接 [摘要]本文介绍C# WinForm多线程开发之ThreadPool 与 Timer,并提供详细的示例代码供参考。

    原文地址:点击打开链接

    [摘要]本文介绍C# WinForm多线程开发之ThreadPool 与 Timer,并提供详细的示例代码供参考。

    本文接上文,继续探讨WinForm中的多线程问题,再次主要探讨threadpool 和timer。

    一 、ThreadPool

    线程池(ThreadPool)是一种相对较简单的方法,它适应于一些需要多个线程而又较短任务(如一些常处于阻塞状态的线程),它的缺点是对创建的线程不能加以控制,也不能设置其优先级。由于每个进程只有一个线程池,当然每个应用程序域也只有一个线程池(对线),所以你将发现 ThreadPool类的成员函数都为static!当你首次调用ThreadPool.QueueUserWorkItem、 ThreadPool.RegisterWaitForSingleObject等,便会创建线程池实例。下面我就线程池当中的两函数作一介绍:

    public static bool QueueUserWorkItem( //调用成功则返回true 
    	    WaitCallback callBack,//要创建的线程调用的委托 
    	    object state //传递给委托的参数 
    	    )//它的另一个重载函数类似,只是委托不带参数而已
        此函数的作用是把要创建的线程排队到线程池,当线程池的可用线程数不为零时(线程池有创建线程数的限制,缺身值为25),便创建此线程,否则就排队到线程池等到它有可用的线程时才创建。

    public static RegisteredWaitHandle RegisterWaitForSingleObject( 
        WaitHandle waitObject,// 要注册的 WaitHandle 
        WaitOrTimerCallback callBack,// 线程调用的委托 
        object state,//传递给委托的参数 
        int TimeOut,//超时,单位为毫秒, 
        bool executeOnlyOnce //是否只执行一次 
    ); 
    public delegate void WaitOrTimerCallback( 
        object state,//也即传递给委托的参数 
        bool timedOut//true表示由于超时调用,反之则因为waitObject 
    );

    此函数的作用是创建一个等待线程,一旦调用此函数便创建此线程,在参数waitObject变为终止状态或所设定的时间TimeOut到了之前,它都处于 “阻塞”状态,值得注意的一点是此“阻塞”与Thread的WaitSleepJoin状态有很大的不同:当某Thread处于 WaitSleepJoin状态时CPU会定期的唤醒它以轮询更新状态信息,然后再次进入WaitSleepJoin状态,线程的切换可是很费资源的;而用此函数创建的线程则不同,在触发它运行之前,CPU不会切换到此线程,它既不占用CPU的时间又不浪费线程切换时间,但CPU又如何知道何时运行它?实际上线程池会生成一些辅助线程用来监视这些触发条件,一旦达到条件便启动相应的线程,当然这些辅助线程本身也占用时间,但是如果你需创建较多的等待线程时,使用线程池的优势就越加明显。

    更详细内容demo:

    namespace TestMethodInvoker
    {
        public partial class Form2 : Form
        {
            public Form2()
            {
                InitializeComponent();
            }
            private void button1_Click(object sender, EventArgs e)
            {
                //ThreadPool.RegisterWaitForSingleObject(
                //    ev,
                //    new WaitOrTimerCallback(WaitThreadFunc),
                //    4,
                //    2000,
                //    false//表示每次完成等待操作后都重置计时器,直到注销等待 
                //    );
                ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadFunc), "test1");
                //Thread.Sleep(10000);
            }
            private delegate void MyInvokeDelegate(string name);
            private void Test(object o)
            {
                richTextBox1.Text += string.Format("the object is {0} \n", o);
            }
            public  void ThreadFunc(object b)
            {
                this.Invoke(new MyInvokeDelegate(Test), b);
            }
            public void WaitThreadFunc(object b, bool t)
            {
                richTextBox1.Text += string.Format("the object is {0},t is {1}\n", b, t);
            } 
        }
    }
         一个很值得扩展的地方时,这里的invoke 用的是代理,其实还有其他的方法,比如 action 和func。实例代码如下:

    this.Invoke(new Action<string>(this.ChangeText), o.ToString());
    this.Invoke(new Action(delegate() { this.textBox1.Text = o.ToString();}));
    private void DoSomething(object o) 
    {
        System.Func<string, int> f = new Func<string, int>(this.GetId);
        object result = this.Invoke(f, o.ToString());
        MessageBox.Show(result.ToString());
    }
    private int GetId(string name) 
    {
        this.textBox1.Text = name;
        if (name == "Y") 
        {
           return 999;
        }
        else 
        {
            return 0;
        }
    }

    二、 Timer

    它适用于需周期性调用的方法,它不在创建计时器的线程中运行,它在由系统自动分配的单独线程中运行。这和Win32中的SetTimer方法类似。它的构造为:

    public Timer( 
        TimerCallback callback,//所需调用的方法 
        object state,//传递给callback的参数 
        int dueTime,//多久后开始调用callback 
        int period//调用此方法的时间间隔 
    );//

    如果 dueTime 为0,则 callback 立即执行它的首次调用。如果 dueTime 为 Infinite,则 callback 不调用它的方法。计时器被禁用,但使用 Change 方法可以重新启用它。如果 period 为0或 Infinite,并且 dueTime 不为 Infinite,则 callback 调用它的方法一次。计时器的定期行为被禁用,但使用 Change 方法可以重新启用它。如果 period 为零 (0) 或 Infinite,并且 dueTime 不为 Infinite,则 callback 调用它的方法一次。计时器的定期行为被禁用,但使用 Change 方法可以重新启用它。 

    在创建计时器之后若想改变它的period和dueTime,我们可以通过调用Timer的Change方法来改变:

    public bool Change( 
        int dueTime, 
        int period 
    );//
        显然所改变的两个参数对应于Timer中的两参数。

    展开全文
  • C# WinForm多线程(三)---- Control.Invoke 下面我们就把在Windows Form软件中使用Invoke时的多线程要注意的问题给大家做一个介绍。  首先,什么样的操作需要考虑使用多线程?总的一条就是,负责与用户交互...

    http://www.cnblogs.com/joechen/archive/2009/04/30/1446857.html

    C# WinForm多线程(三)---- Control.Invoke

    下面我们就把在Windows Form软件中使用Invoke时的多线程要注意的问题给大家做一个介绍。

          首先,什么样的操作需要考虑使用多线程?总的一条就是,负责与用户交互的线程(以下简称为UI线程)应该保持顺畅,当UI线程调用的API可能引起阻塞时间超过30毫秒时(比如访问CD-ROM等速度超慢的外设、进行远程调用等等)就应该考虑使用多线程。为什么是30毫秒?30毫秒的概念是人眼可以察觉到的一个迟滞,大约等同于电影里的一帧停留的时间,最长不要超过100毫秒。

        第二,最方便和简单的多线程是使用线程池。通过线程池里的线程运行代码的最简便方法则是使用异步委托调用。注意委托调用通常是同步完成的,请使用BeginInvoke方法,这样就可以把要调用的方法排队到线程池里等候处理,而程序的流程会立刻返回到调用方(此处是UI线程),而调用方因此不会出现阻塞。

        看看下面的例子我们就发现要使用线程池异步执行代码也并非十分复杂,这里我们利用System.Windows.Forms.MethodInvoker委托进行异步调用。注意MethodInvoker委托不接受方法参数,如果需要向异步执行的方法传递参数,请使用其他委托,或者需要自己定义。


    private void StartSomeWorkFromUIThread () {
        
    // 我们要做的工作相对UI线程而言台慢了,用下面的方法异步进行处理
        MethodInvoker mi = new MethodInvoker(RunsOnWorkerThread);//这是入口方法
        mi.BeginInvoke(nullnull); // 这样就不会阻塞
    }

    // 缓慢的工作在此方法内进行处理,使用线程池里的线程
    private void RunsOnWorkerThread() {
        DoSomethingSlow();
    }

     

    归纳上述方法,对UI线程而言实际上就是:1、发出调用,2、立刻返回,具体运行过程不理了,这样UI线程就不会被阻塞。这种方法很重要,下面我们会深入介绍。除了上面的方法,还有其他使用线程池的方法,当然如果你高兴也可以自己创建线程。

        第三,在Windows Form中使用多线程的,最重要的一条注意事项是,除了创建控件的线程以外,绝对不要在任何其他线程里面调用控件的成员(只有极个别情况例外),也就是说控件属于创建它的线程,不能从其他线程里面访问。这一条适用于所有从System.Windows.Forms.Control派生的控件(因此可以说是几乎所有控件),包括Form控件本身也是。举一反三,我们很容易得出这样的结论,控件的子控件必须由创建控件的线程来创建,比如一个表单上的按钮,比如由创建表单的线程来创建,因此,一个窗口中的所有控件实际上都活在同一个线程之中。在实际编程时,大多数的软件的做法都是让同一线程负责全部的控件,这就是我们所说的UI线程。看下面的例子:

    //  这是由UI线程定义的Label控件
    private  Label lblStatus;
    .
    //  以下方法不在UI线程上执行
    private   void  RunsOnWorkerThread() {
        DoSomethingSlow();
        lblStatus.Text 
    =   " Finished! " ;     //  这是错的
    }

     

    我们要特别提醒大家,很多人刚开始的时候都会使用以上的方法来访问不在同一个线程里的控件(包括笔者本人),而且在1.0版.Net 框架上似乎没有发现问题,但是这根本就是错的,更糟糕的是,程序员在这里不会得到任何错误提示,一开始就上当受骗,之后会莫明其妙地发现其他错误,这就是Windows Form多线程编程的痛苦所在。笔者试过花很多时间来Debug自己写的Splash窗口突然消失的问题,结果还是失败了:笔者在软件的引导过程中,用另外一个线程里创建了一个Splash窗口来显示欢迎信息,然后尝试把主线程里引导的状态直接写入到Splash窗口上的控件中,开始还OK,可是过一会Splash窗口就莫明其妙消失了。

        理解了这一点,我们应该留意到,有时候即使没有用System.Threading.Thread来显式创建一个线程,我们也可能因为使用了异步委托的BeginInvoke方法来隐式创建了线程(从线程池里),在这种线程里也同样不能调用UI线程所创建的控件的成员。

        第四,由于上述限制,我们可能会感到很不方便,的确,当我们利用一个新创建的线程来执行某些花时间的运算时,怎样知道运算进度如何并通过UI反映给用户呢?解决方法很多!比如熟悉多线程编程的用户很快会想到,我们采用一些低级的同步方法,工作者线程把状态保存到一个同步对象中,让UI线程轮询(Polling)该对象并反馈给用户就可以了。不过,这还是挺麻烦的,实际上不用这样做,Control类(及其派生类)对象有一个Invoke方法很特别,这是少数几个不受线程限制的成员之一。我们前面说到,绝对不要在任何其他线程里面调用非本线程创建的控件的成员时,也说了“只有极个别情况例外”,这个Invoke方法就是极个别情况之一----Invoke方法可以从任何线程里面调用。下面我们来讲解Invoke方法。

        Invoke方法的参数很简单,一个委托,一个参数表(可选),而Invoke方法的主要功能就是帮助你在UI线程(即创建控件的线程)上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。注意,使用Invoke方法时,UI线程不能处于阻塞状态。以下MSDN里关于Invoke方法的说明:

        “控件上有四种方法可以安全地从任何线程进行调用:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。对于所有其他方法调用,则应使用调用 (invoke) 方法之一封送对控件的线程的调用。
        委托可以是 EventHandler 的实例,在此情况下,发送方参数将包含此控件,而事件参数将包含 EventArgs.Empty。委托还可以是 MethodInvoker 的实例或采用 void 参数列表的其他任何委托。调用 EventHandler 或 MethodInvoker 委托比调用其他类型的委托速度更快。”

        好了,说完Invoke,顺便说说BeginInvoke,毫无疑问这是Invoke的异步版本(Invoke是同步完成的),不过大家不要和上面的System.Windows.Forms.MethodInvoker委托中的BeginInvoke混淆,两者都是利用不同线程来完成工作,但是控件的BeginInvoke方法总是使用UI线程,而其他的异步委托调用方法则是利用线程池里的线程。相对Invoke而言,使用BeginInvoke稍稍麻烦一点,但还是那句话,异步比同步效果好,尽管复杂些。比如同步方法可能出现这样一种死锁情况:工作者线程通过Invoke同步调用UI线程里的方法时会阻塞,而万一UI线程正在等待工作者线程做某件事时怎么办?因此,能够使用异步方法时应尽量使用异步方法。

        下面我们利用所学到的知识来改写上面那个简单的例子:


    // 这是由UI线程定义的Label控件
    private Label lblStatus;
    .
    // 以下方法不在UI线程上执行
    private void RunsOnWorkerThread() {
        DoSomethingSlow();
        
    // Do UI update on UI thread
        object[] pList = { this, System.EventArgs.Empty };
        lblStatus.BeginInvoke(
          
    new System.EventHandler(UpdateUI), pList);
    }
    .


    // 切换回UI线程执行的入口
    private void UpdateUI(object o, System.EventArgs e) {
        
    //现在没问题了,使用Invoke使得线程总是回到UI线程,所以我们可以放心大胆地调用控件的成员了
        lblStatus.Text = "Finished!";
    }

     

    第五,关于多线程编程还要考虑线程之间的同步问题、死锁和争用条件,有关这类问题的文章很多,我们就不赘述了。



    展开全文
  • 多线程开发例子 开发工具: VS2017 版本:framework4.6.1
  • c# winform 多线程使用的问题 多线登录new新窗体的时候 ``` Thread Thr=new Thread(new ThreadStart(test1)) Thr.Start(); public void test1() { form1 for=new form1(); for.show(); } ``` 报 线程间操作...
  • C#新手。。。C#程序调用dll,dll中有一个回调函数用于接受数据,想根据接收到的数据增加treeview节点,怎么操作?直接在回调函数中程序会崩溃。 nt iSize = cJSON_GetArraySize(iptr11); for (int i = 0; i ; i++)...
  • c#winform多线程感想

    2018-07-20 14:22:00
    最近在做一个关于识别的项目,手动识别和自动识别,为了更好的保证自动识别不会引起界面的卡顿等现象,所以简单的学习了一下多线程,也只是入门但还是记录一下。 一、首先了解一下用多线程的理由 1、可以使用...
  • C#winform多线程问题

    2019-05-24 18:08:39
    方法B是有ref值标记是否执行完毕的,方法B要根据界面选择个数次执行。要如何实现呢? 我是用while循环检测方法B是否执行完,执行完就执行下一次,但是委托A只能得到进度,无法更新到界面显示。 如果把while去掉,...
  • WinForm多线程开发之Control.Invoke,并提供详细的示例代码供参考。 下面我们就把在Windows Form软件中使用Invoke时的多线程要注意的问题给大家做一个介绍。 首先,什么样的操作需要考虑使用多线程?总的一...
  • 想做一个多线程任务中的信息提示窗体。即在执行任务的过程中,如果一个线程需要向外界输出信息,那么就显示该信息窗体,并在窗体上显示信息内容,可以点击一个按钮关闭该窗体。如果其中一个线程调用了该窗体显示信息...
  • WinForm多线程开发之Thread类库,并提供简单的示例代码供参考。 Windows是一个多任务的系统,如果你使用的是windows 2000及其以上版本,你可以通过任务管理器查看当前系统运行的程序和进程。什么是进程呢?当...
  • C# WinForm 多线程 基类

    千次阅读 2012-12-20 10:25:01
    如果在C# WinForm开发多线程程序,多线程中的方法不能直接更新主线程上的对象(比如UI),但可以通过委托和事件更新主线程上的对象。源代码下载http://download.csdn.net/detail/kmblack1/4909697,示例如下: ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,557
精华内容 3,422
关键字:

c#winform多线程

c# 订阅