关于C#线程 任务

跨线程访问

1.需求

2.脚本

执行一个任务

csharp 复制代码
    //通过跨线程给控件赋值
        private void btnExecute1_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(() =>
             {
                 for (int i = 0; i < 10; i++)
                 {
                     // this.lblResult1.Text = i.ToString();
                     if (this.lblResult1.InvokeRequired)
                     {
                         this.lblResult1.Invoke(
                             new Action<string>(data => { this.lblResult1.Text = data; }),
                             i.ToString()
                             );

                         Thread.Sleep(300);
                     }
                 }
             });
            thread.IsBackground = true;
            thread.Start();
        }

通过线程读取值

csharp 复制代码
  private void btnRead_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(() =>
             {
                 this.txtV.Invoke(new Action<string>(data =>
                 {
                     this.lblV.Text = data;//将读取的控件值,在其他控件中显示出来
                 }), this.txtV.Text);
             });
            thread.IsBackground = true;
            thread.Start();


        }

跨线程访问数据库

1.需求

2.脚本

执行任务一

csharp 复制代码
private void btnExecute1_Click(object sender, EventArgs e)
{
	Thread thread = new Thread(() =>
 {
string classCount = DBUtility.SQLHelper.GetSingleResult("select count(*) from Products").ToString();
 this.lblResult1.Invoke(new Action<string>(count => { this.lblResult1.Text = count; }), classCount);
});
	thread.IsBackground = true;
	thread.Start();
}

访问数据库

csharp 复制代码
  private void btnGetData_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(() =>
            {
                DataSet ds = DBUtility.SQLHelper.GetDataSet("select * from ProductInventory;select ProductId,ProductName,Unit from Products");
                DataTable dt1 = ds.Tables[0];
                DataTable dt2 = ds.Tables[1];

                this.dgv1.Invoke(new Action<DataTable>(t => { this.dgv1.DataSource = t; }), dt1);
                this.dgv2.Invoke(new Action<DataTable>(t => { this.dgv2.DataSource = t; }), dt2);
            });
            thread.IsBackground = true;
            thread.Start();
        }

3.sqlHelper

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.IO;

namespace DBUtility
{
    /// <summary>
    /// 针对SQLServer数据库的通用访问类
    /// </summary>
    public class SQLHelper
    {

        //封装数据库连接字符串
        private static string connString = "server=.;DataBase=SaleManagerDB;Uid=sa;Pwd=a123456";

    
        public static object GetSingleResult(string sql)
        {
            SqlConnection conn = new SqlConnection(connString);
            SqlCommand cmd = new SqlCommand(sql, conn);
            try
            {
                conn.Open();
                return cmd.ExecuteScalar();
            }
            catch (Exception ex)
            {
                //将异常信息写入日志   。。。           

                throw ex;
            }
            finally
            {
                conn.Close();
            }
        }
        public static SqlDataReader GetReader(string sql)
        {
            SqlConnection conn = new SqlConnection(connString);
            SqlCommand cmd = new SqlCommand(sql, conn);
            try
            {
                conn.Open();
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);
            }
            catch (Exception ex)
            {
                conn.Close();
                //将异常信息写入日志   。。。           

                throw ex;
            }
        }
        public static DataSet GetDataSet(string sql)
        {
            SqlConnection conn = new SqlConnection(connString);
            SqlCommand cmd = new SqlCommand(sql, conn);
            SqlDataAdapter da = new SqlDataAdapter(cmd);//创建数据适配器对象
            DataSet ds = new DataSet();//创建一个内存数据集
            try
            {
                conn.Open();
                da.Fill(ds);//使用数据适配器填充数据集
                return ds;
            }
            catch (Exception ex)
            {
                //将异常信息写入日志   。。。           

                throw ex;
            }
            finally
            {
                conn.Close();
            }
        }

    }
}

线程生命周期管理

1.需求

2.脚本

公共变量

csharp 复制代码
private Thread thread = null;
        private int counter = 0;

开启start

csharp 复制代码
    private void btnStart_Click(object sender, EventArgs e)
        {
            thread = new Thread(() =>
             {
                 while (true)
                 {
                     try
                     {
                        Thread.Sleep(500);
                         lblInfo.Invoke(new Action(() =>
                         {
                             lblInfo.Text += counter++ + ",";
                         }));
                     }
                     catch (Exception ex)
                     {
                         MessageBox.Show(ex.Message + "  异常位置:" + counter++);
                     }
                 }
             });
            thread.Start();
        }

暂停

csharp 复制代码
    private void btnSuspend_Click(object sender, EventArgs e)
        {
            if (thread.ThreadState == ThreadState.Running ||
                thread.ThreadState == ThreadState.WaitSleepJoin)
            {
                thread.Suspend();
            }
        }

继续

csharp 复制代码
   private void btnResume_Click(object sender, EventArgs e)
        {
            if (thread.ThreadState == ThreadState.Suspended )
            {
                thread.Resume();
            }
        }

中断

csharp 复制代码
  private void btnInterrupt_Click(object sender, EventArgs e)
        {
            thread.Interrupt();
        }

中止

csharp 复制代码
    private void btnAbort_Click(object sender, EventArgs e)
        {
            thread.Abort();
        }

Join

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace D02_3_ThreadJoinTest
{
    class Program
    {
        //线程等待, task.wait()
        static void Main(string[] args)
        {
  
            Thread thread = new Thread(new ThreadStart(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine("这个是正在执行的子线程数据......");
            }));

            thread.Start();

           thread.Join();//会等待子线程执行完毕后,在执行下面的主线程内容。

            Console.WriteLine("这个是主线程的数据...");

            Console.Read();
        }

    }
}

线程池

线程池ThreadPool的基本使用

csharp 复制代码
    static void Method11()
        {
            ThreadPool.QueueUserWorkItem((arg) =>
                {
                    //请在这里编写实际的要处理的内容...

                    Console.WriteLine("子线程Id:" + Thread.CurrentThread.ManagedThreadId);
                });
            Console.WriteLine("主线程Id:" + Thread.CurrentThread.ManagedThreadId);
        }
csharp 复制代码
 static void Method12()
        {
            ThreadPool.QueueUserWorkItem((arg) =>
            {
                //请在这里编写实际的要处理的内容...

                Console.WriteLine("子线程Id:" + Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine("arg=" + arg);

            }, "com");
            Console.WriteLine("主线程Id:" + Thread.CurrentThread.ManagedThreadId);
        }

线程和ThreadPool和Thread性能比较

csharp 复制代码
        static void Method2()
        {
            for (int i = 1; i <= 10; i++)
            {
                Thread thread = new Thread(() =>
                 {
                     Console.WriteLine($"子线程{i}   Id:" + Thread.CurrentThread.ManagedThreadId);
                     for (int a = 1; a <= 5; a++)
                     {
                         Console.WriteLine(a);
                     }
                 });
                thread.Name = "线程名称:" + i;
                thread.IsBackground = true;
                thread.Start();

                Thread.Sleep(50);
            }
        }
csharp 复制代码
        static void Method3()
        {
            Console.WriteLine("------------------------使用线程池----------------------------");

            for (int i = 1; i <= 10; i++)
            {

                ThreadPool.QueueUserWorkItem((arg) =>
                {
                    Console.WriteLine($"子线程{i}   Id:" + Thread.CurrentThread.ManagedThreadId);
                    for (int a = 1; a <= 5; a++)
                    {
                        Console.WriteLine(a);
                    }
                });


                Thread.Sleep(50);
            }
        }

入口函数测试

csharp 复制代码
       static void Main(string[] args)
        {

            //   Method11();
           // Method12();
            Method2();
          //  Method3();

            Console.Read();
        }

Task

Task使用【1】多线程任务的开启3种方式

csharp 复制代码
   //【1】通过new的方式创建一个Task对象,并启动

        static void Method1_1()
        {
            Task task1 = new Task(() =>
            {
                //在这个地方编写我们需要的逻辑...

                Console.WriteLine($"new一个新的Task启动的子线程Id={Thread.CurrentThread.ManagedThreadId}");
            });
            task1.Start();
        }
csharp 复制代码
        //【2】使用Task的Run()方法
        static void Method1_2()
        {
            Task task2 = Task.Run(() =>
              {
                  //在这个地方编写我们需要的逻辑...

                  Console.WriteLine($"使用Task的Run()方法开启的子线程Id={Thread.CurrentThread.ManagedThreadId}");
              });
        }
csharp 复制代码
   //【3】使用TaskFactory启动(类似于ThreadPool)
        static void Method1_3()
        {
            Task task3 = Task.Factory.StartNew(() =>
            {
                //在这个地方编写我们需要的逻辑...

                Console.WriteLine($"使用TaskFactory开启的子线程Id={Thread.CurrentThread.ManagedThreadId}");
            });
        }

Task使用【2】Task的阻塞方式和任务延续

【1】回顾之前使用Thread多个子线程执行时阻塞的方法
csharp 复制代码
 static void Method2()
        {
            Thread thread1 = new Thread(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine("Child Thread (1)......");
            });
            Thread thread2 = new Thread(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("Child Thread (2)......");
            });
            thread1.Start();
            thread2.Start();
            //...

            thread1.Join();//让调用线程阻塞
            thread2.Join();
            //如果有很多的thread,是不是也得有很多的Join?还有,我们只希望其中一个执行完以后,后面的其他线程就能执行,这个也做不了!

            Console.WriteLine("This is Main Thread!");
        }
【2】Task各种【阻塞】方式(3个)
csharp 复制代码
        static void Method3()
        {
            Task task1 = new Task(() =>
             {
                 Thread.Sleep(1000);
                 Console.WriteLine($"Task1子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
             });
            task1.Start();
            Task task2 = new Task(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine($"Task2子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
            });
            task2.Start();

            ////第1种方式:挨个等待和前面一样
            //task1.Wait();
            //task2.Wait();

            ////第2种方式:等待所有的任务完成    【推荐】
            //Task.WaitAll(task1, task2);

            //第3种方式:等待任何一个完成即可  【推荐】
            Task.WaitAny(task1, task2);

            Console.WriteLine("主线程开始运行!Time=" + DateTime.Now.ToLongTimeString());
        }
Task任务的延续:WhenAll 希望前面所有任务执行完毕后,再继续执行后面的线程,和前面相比,既有阻塞,又有延续
csharp 复制代码
      static void Method4()
        {
            Task task1 = new Task(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Task1子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
            });
            task1.Start();
            Task task2 = new Task(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine($"Task2子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
            });
            task2.Start();

            //线程的延续(主线程不等待,子线程依次执行,如果你需要主线程也按照子线程的顺序来,请你自己把主线程的任务放到延续任务中就可以)
            Task.WhenAll(task1, task2).ContinueWith(task3 =>
             {
                 //在这里可以编写你需要的业务...

                 Console.WriteLine($"Task3子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
             });

            Console.WriteLine("主线程开始运行!Time=" + DateTime.Now.ToLongTimeString());
        }
Task的延续:WhenAny
csharp 复制代码
static void Method5()
        {
            Task task1 = new Task(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Task1子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
            });
            task1.Start();
            Task task2 = new Task(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine($"Task2子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
            });
            task2.Start();

            //线程的延续(主线程不等待,子线程任何一个执行完毕,就会执行后面的线程)
            Task.WhenAny(task1, task2).ContinueWith(task3 =>
            {
                //在这里可以编写你需要的业务...

                Console.WriteLine($"Task3子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
            });

            Console.WriteLine("主线程开始运行!Time=" + DateTime.Now.ToLongTimeString());
        }

Task使用【3】Task常见枚举 TaskCreationOptions(父子任务运行、长时间运行的任务处理)

Task的构造方法,观察TaskCreationOptions这个枚举的类型,自己通过F12查看
csharp 复制代码
     static void Method6()
        {
            Task parentTask = new Task(() =>
             {
                 Task task1 = new Task(() =>
                  {
                      Thread.Sleep(1000);
                      Console.WriteLine($"Task1子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
                  }, TaskCreationOptions.AttachedToParent);

                 Task task2 = new Task(() =>
                 {
                     Thread.Sleep(3000);
                     Console.WriteLine($"Task2子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
                 }, TaskCreationOptions.AttachedToParent);
                 task1.Start();
                 task2.Start();
             });

            parentTask.Start();
            parentTask.Wait();//等待附加的子任务全部完成。相当于Task.WaitAll(taks1,task2);
            //TaskCreationOptions.AttachedToParent如果这个枚举参数不添加,主线程会直接运行,不等待
            Console.WriteLine("主线程开始执行!Time=  " + DateTime.Now.ToLongTimeString());
        }
长时间的任务运行,需要采取的方法
csharp 复制代码
 static void Method7()
        {
            Task task1 = new Task(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine($"Task1子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
            }, TaskCreationOptions.LongRunning);

            //LongRunning:如果你明确知道这个任务是长时间运行的,建议你加上。当然你使用Thread也是可以的。但是不要使用
            //ThreadPool,因为长时间占用不归还线程,系统会强制开启新的线程,会一定程度影响性能
            task1.Start();
            task1.Wait();

            Console.WriteLine("主线程开始执行!Time=  " + DateTime.Now.ToLongTimeString());
        }

Task使用【4】Task中的取消功能:使用的是CacellationTokenSoure解决多任务中协作取消和超时取消方法

Task任务的取消和判断
csharp 复制代码
  static void Method8()
        {
            //创建取消信号源对象
            CancellationTokenSource cts = new CancellationTokenSource();
            Task task = Task.Factory.StartNew(() =>
              {
                  while (!cts.IsCancellationRequested)//判断任务是否被取消
                  {
                      Thread.Sleep(200);

                      Console.WriteLine($"子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
                  }
              }, cts.Token);

            //我们在这个地方模拟一个事件产生
            Thread.Sleep(2000);
            cts.Cancel();//取消任务,只要传递这样一个信号就可以
        }
【2】Task任务取消:同时我们也希望做一些清理的工作,也就是取消这个动作会触发一个任务
csharp 复制代码
  static void Method9()
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            Task task = Task.Factory.StartNew(() =>
            {
                while (!cts.IsCancellationRequested)
                {
                    Thread.Sleep(500);

                    Console.WriteLine($"子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
                }
            }, cts.Token);

            //注册一个委托:这个委托将在任务取消的时候调用
            cts.Token.Register(() =>
            {
                //在这个地方可以编写自己要处理的逻辑...
                Console.WriteLine("任务取消,开始清理工作......");
                Thread.Sleep(2000);
                Console.WriteLine("任务取消,清理工作结束......");
            });

            //这个地方肯定是有其他的逻辑来控制取消
            Thread.Sleep(3000);//模拟其他的耗时工作
            cts.Cancel();//取消任务
        }
3】Task任务延时自动取消:比如我们请求一个远程接口,如果长时间没有返回数据,我们可以做一个时间限制,超时可以取消任务(比如微信红包退回)
csharp 复制代码
 static void Method10()
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            // CancellationTokenSource cts = new CancellationTokenSource(3000);
            Task task = Task.Factory.StartNew(() =>
            {
                while (!cts.IsCancellationRequested)
                {
                    Thread.Sleep(300);

                    Console.WriteLine($"子线程Id={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");
                }
            }, cts.Token);

            //注册一个委托:这个委托将在任务取消的时候调用
            cts.Token.Register(() =>
            {
                //在这个地方可以编写自己要处理的逻辑...
                Console.WriteLine("任务取消,开始清理工作......");
                Thread.Sleep(2000);
                Console.WriteLine("任务取消,清理工作结束......");
            });


            cts.CancelAfter(3000); //3秒后自动取消
        }

Task使用【5】Task中专门的异常处理:AggregateException

csharp 复制代码
        //AggregateException:是一个异常集合,因为Task中可能抛出异常,所以我们需要新的类型来收集异常对象
        static void Method11()
        {
            var task = Task.Factory.StartNew(() =>
            {
                var childTask1 = Task.Factory.StartNew(() =>
                {
                    //实际开发中这个地方写你处理的业务,可能会发生异常....

                    //自己模拟一个异常
                    throw new Exception("my god!Exception from childTask1 happend!");
                }, TaskCreationOptions.AttachedToParent);

                var childTask2 = Task.Factory.StartNew(() =>
                {
                    throw new Exception("my god!Exception from childTask2 happend!");
                }, TaskCreationOptions.AttachedToParent);
            });
            try
            {
                try
                {
                    task.Wait();   //1.异常抛出的时机
                }
                catch (AggregateException ex)  //2.异常所在位置
                {
                    foreach (var item in ex.InnerExceptions)
                    {
                        Console.WriteLine(item.InnerException.Message + "     " + item.GetType().Name);
                    }

                    //3.异常集合,如果你想往上抛,需要使用Handle方法处理一下
                    ex.Handle(p =>
                    {
                        if (p.InnerException.Message == "my god!Exception from childTask1 happend!")
                            return true;
                        else
                            return false; //返回false表示往上继续抛出异常
                    });
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("-----------------------------------------------------");
                Console.WriteLine(ex.InnerException.InnerException.Message);
            }
        }

监视锁:Lock 限制线程个数的一把锁

为什么要用锁?在多线程中,尤其是静态资源的访问,必然会有竞争

csharp 复制代码
 //为什么要用锁?在多线程中,尤其是静态资源的访问,必然会有竞争

        private static int nums = 0;
        private static object myLock = new object();
        static void Method12()
        {
            for (int i = 0; i < 5; i++)
            {
                Task.Factory.StartNew(() =>
                {
                    TestMethod();
                });
            }
        }

        static void TestMethod()
        {
            for (int i = 0; i < 100; i++)
            {
                lock (myLock)
                {
                    nums++;
                    Console.WriteLine(nums);
                }
            }
        }
        //Lock是Monitor语法糖,本质是解决资源的锁定问题
        //我们锁住的资源一定是让线程可访问到的,所以不能是局部变量。
        //锁住的资源千万不要是值类型。
        //lock也不能锁住string类型。
  • 原因一:解决多线程并发访问共享资源的冲突问题
    • 在多线程环境下,多个线程可能同时访问和修改共享资源(如全局变量、静态变量、对象的实例字段等)。如果没有适当的同步机制,就可能导致数据不一致或不可预期的结果。例如,假设有一个银行账户类,有一个Balance属性表示账户余额,还有一个Withdraw方法用于取款。
csharp 复制代码
     class BankAccount
     {
         public decimal Balance { get; set; }
         public void Withdraw(decimal amount)
         {
             Balance -= amount;
         }
     }     class BankAccount
     {
         public decimal Balance { get; set; }
         public void Withdraw(decimal amount)
         {
             Balance -= amount;
         }
     }
  • 如果有两个线程同时调用Withdraw方法,就可能出现问题。假设初始余额是100,两个线程都试图取出50。在没有同步的情况下,可能会出现这样的执行顺序:

    • 线程 1 读取Balance的值为100
    • 线程 2 读取Balance的值也为100
    • 线程 1 将Balance更新为100 - 50 = 50
    • 线程 2 也将Balance更新为100 - 50 = 50
  • 这样,最终余额是50,而不是正确的0,这就是所谓的数据竞争。使用锁可以确保在一个线程访问共享资源时,其他线程必须等待,从而避免这种数据竞争。

  • 原因二:保证代码块的原子性

    • 原子操作是指不可被中断的一个或一系列操作。锁可以用来将一段代码标记为原子操作。例如,在一个复杂的计算过程中,可能涉及多个步骤来更新共享数据,这些步骤必须作为一个整体来执行,不能被其他线程干扰。
    • 比如,有一个计数器,每次需要先读取当前值,然后进行加 1 操作,最后再保存回去。这三个步骤应该作为一个原子操作来执行。
csharp 复制代码
class Counter
     {
         private int count;
         public int Increment()
         {
             int temp;
             lock (this)
             {
                 temp = count;
                 count = temp + 1;
             }
             return count;
         }
     }
  1. **lock**关键字的使用场景
    • 场景一:多个线程访问和修改同一个对象的状态时
      • 当多个线程对同一个对象的属性或方法进行读写操作时,需要使用lock。例如,在一个游戏中,有一个Player对象,它有Health(生命值)和Score(分数)属性。多个线程可能会同时更新玩家的生命值(例如受到伤害或恢复生命)和分数(例如完成任务或击杀敌人)。
csharp 复制代码
     class Player
     {
         public int Health { get; set; }
         public int Score { get; set; }
         public void TakeDamage(int damage)
         {
             lock (this)
             {
                 Health -= damage;
             }
         }
         public void AddScore(int points)
         {
             lock (this)
             {
                 Score += points;
             }
         }
     }
  • 场景二:共享资源的初始化过程
    • 有些资源的初始化可能比较复杂且只需要执行一次,但可能会被多个线程同时访问。例如,一个数据库连接池,在应用程序启动时需要初始化连接池中的连接。
csharp 复制代码
     class DatabaseConnectionPool
     {
         private static DatabaseConnectionPool instance;
         private List<DatabaseConnection> connections;
         private static object lockObject = new object();
         private DatabaseConnectionPool()
         {
             // 初始化连接池
             connections = new List<DatabaseConnection>();
             // 添加连接到连接池的代码省略
         }
         public static DatabaseConnectionPool Instance
         {
             get
             {
                 if (instance == null)
                 {
                     lock (lockObject)
                     {
                         if (instance == null)
                         {
                             instance = new DatabaseConnectionPool();
                         }
                     }
                 }
                 return instance;
             }
         }
     }

注释事项:

  1. 选择合适锁对象:
    1. 不能为私有变量
    2. 不能是字符串类型或者值类型,是对象
  2. 避免死锁 死锁是指两个或多个线程互相等待对方释放资源的情况。
    1. 例如,线程 A 持有锁 1,等待锁 2,而线程 B 持有锁 2,等待锁 1。为了避免死锁,要确保线程获取锁的顺序是一致的,并且尽量减少锁的嵌套和持有时间过长的情况。

跨线程UI空间

需求

脚本

异步更新数据

csharp 复制代码
        private void btnUpdate_Click(object sender, EventArgs e)
        {
            Task task = new Task(() =>
             {
                 this.lblInfo.Text = "来自Task的数据更新:我们正在学习多线程!";
             });
            //task.Start();  //这样使用会报错

            //使用下面的方式解决报错的问题
            task.Start(TaskScheduler.FromCurrentSynchronizationContext());
        }

耗时任务解决1

csharp 复制代码
   //针对UI耗时的情况,单独重载其实并不是很好
        private void btnUpdate_Click1(object sender, EventArgs e)
        {
            Task task = new Task(() =>
            {
                //模拟耗时(这个地方会卡主)
                Thread.Sleep(5000);
                this.lblInfo.Text = "来自Task的数据更新:我们正在学习多线程!";
            });
            //task.Start();  //这样使用会报错

            //使用下面的方式解决报错的问题
            task.Start(TaskScheduler.FromCurrentSynchronizationContext());
        }

耗时任务解决2

csharp 复制代码
  //针对耗时任务,我们可以使用新的方法
        private void btnUpdate_Click2(object sender, EventArgs e)
        {
            this.btnUpdate.Enabled = false;
            this.lblInfo.Text = "数据更新中,请等待......";
            Task task =Task.Factory.StartNew(() =>
            {            
                Thread.Sleep(5000); //有耗时的任务,我们可以放到ThreadPool中             
            });

            //在ContinueWith中更新我们的数据
            task.ContinueWith(t =>
            {
                this.lblInfo.Text = "来自Task的数据更新:我们正在学习多线程!";
                this.btnUpdate.Enabled = true;
            },TaskScheduler.FromCurrentSynchronizationContext()); //更新操作到同步的上下文中           
         
        }
相关推荐
ID_180079054732 小时前
Python调用淘宝评论API:从入门到首次采集全流程
服务器·数据库·python
uoKent2 小时前
MySQL 游标(Cursor)详解:与存储过程的结合使用
数据库·mysql
小猪咪piggy2 小时前
【Python】(2) 执行顺序控制语句
开发语言·python
Web极客码2 小时前
宝塔面板后台突然显示“IO延迟非常高”
linux·服务器·数据库
Σdoughty2 小时前
python第三次作业
开发语言·前端·python
是萧萧吖2 小时前
每日一练——有效的括号
java·开发语言·javascript
zhihuaba2 小时前
构建一个基于命令行的待办事项应用
jvm·数据库·python
MediaTea2 小时前
Python:内置类型也是类对象
开发语言·python
gpldock2222 小时前
Flutter App Templates Deconstructed: A 2025 Architectural Review
开发语言·javascript·flutter·wordpress