多线程

目录--作者:hnZheng
	* 
线程池以异步的方式执行一个方法
	* 
线程执行上下文
	* 
在异步线程间取消上下文的流动
	* 
协作式取消和超时 取消一个正在执行的线程
	* 
System.Threading.Tasks任务的使用
	* 
等待任务完成并且获取结果
	* 
任务完成时启动新的任务
	* 
任务可以启动多个子任务
	* 
任务工厂的使用
	* 
任务调度器 常用
	* 
Paraller的静态For 和ForEach 和 Invoke
	* 
执行定时计算限制操作 定时程序
	* 
同步上下文任务调度器



// 线程池 异步执行一个方法
static void Main(string[] args)
{
#region 实例二 线程池以异步的方式执行一个方法
//实例一 线程池以异步的方式执行一个方法
Console.WriteLine("程序启动开始...");
ThreadPool.QueueUserWorkItem(FirstMethod);
Console.WriteLine("执行其他操作...");
Thread.Sleep(10000);//其他工作执行10秒
Console.WriteLine("执行结束...");
Console.ReadLine();
#endregion
}
// 方法的签名要匹配
public static void FirstMethod(Object state)
{
Thread.Sleep(11000);
Console.WriteLine("执行完FirstMethod...");
}

//实例二 线程执行上下文
static void Main(string[] args)
{
#region 实例二 线程执行上下文
//实例二 线程执行上下文
// 1、将一些数据放在Main线程的逻辑调用上下文中,以下的线程池中加入三个线程
CallContext.LogicalSetData("Name", "zhenghaonan");
ThreadPool.QueueUserWorkItem(state =>
{

Console.WriteLine("This is FirstMethod Name={0}", CallContext.LogicalGetData("Name"));
});

//在异步线程间取消上下文的流动
ExecutionContext.SuppressFlow();
//这个线程中数据没有被流动下来
ThreadPool.QueueUserWorkItem(state =>
{
Console.WriteLine("This is SecondMethod Name={0}", CallContext.LogicalGetData("Name"));
});
// 在异步线程中恢复上下文的流动
ExecutionContext.RestoreFlow();
// 数据继续流动
ThreadPool.QueueUserWorkItem(state =>
{
Console.WriteLine("This is ThridMethod Name={0}", CallContext.LogicalGetData("Name"));
});
GetContextData();
Console.ReadLine();
// 以上实例 执行结果为 只有第二个线程中数据没有,其他的线程数据都有,并且四个线程是异步执行的

#endregion
}
public static void GetContextData()
{
Console.WriteLine("This is GetContextData Name={0}", CallContext.LogicalGetData("Name"));
Console.WriteLine("This is GetContextData");
}

//实例三 协作式取消和超时 取消一个正在执行的线程
static void Main(string[] args)
{
#region 实例三 协作式取消和超时 取消一个正在执行的线程

//实例三 协作式取消和超时 取消一个正在执行的线程
//创建第一个Token
CancellationTokenSource cts = new CancellationTokenSource();
//创建第二个Token
CancellationTokenSource cts1 = new CancellationTokenSource();
//创建一个线程1
ThreadPool.QueueUserWorkItem(o =>
{
for (int i = 0; i < 50; i++)
{
Thread.Sleep(1000);
Console.WriteLine("线程1---{0}", i);
//获取Token,判断线程是否被取消
if (cts.Token.IsCancellationRequested)
{
//当线程取消操作是
Console.WriteLine("线程 1 已经被取消");
break;
}
}
});
//创建一个线程2
ThreadPool.QueueUserWorkItem(o =>
{
for (int i = 0; i < 50; i++)
{
Thread.Sleep(1000);
Console.WriteLine("线程2--{0}", i);
//获取Token,判断线程是否被取消
if (cts1.Token.IsCancellationRequested)
{
//当线程取消操作是
Console.WriteLine("线程 2 已经被取消");
break;
}
}
});


//创建一个Token,关联一个或者多个Token,当cts1取消cts2也会被取消
//CancellationTokenSource cts2 = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token);
CancellationTokenSource cts2 = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts.Token);// 关联多个Token,只要有一个取消,cts2就会被取消
//创建一个线程3
ThreadPool.QueueUserWorkItem(o =>
{
for (int i = 0; i < 50; i++)
{
Thread.Sleep(1000);
Console.WriteLine("线程3--{0}", i);
//获取Token,判断线程是否被取消
if (cts2.Token.IsCancellationRequested)
{
//当线程取消操作是
Console.WriteLine("线程 3 已经被取消");
break;
}
}
});

// 这里可以注册一个或者多个方法在线程取消时执行
//注册的三个方法也是异步执行的
//以下是线程1的Token,线程1取消时进行时的操作
cts.Token.Register(() => { Console.WriteLine("线程1取消时进行的操作0"); });
cts.Token.Register(() => { Console.WriteLine("线程1取消时进行的操作1"); });
cts.Token.Register(() => { Console.WriteLine("线程1取消时进行的操作2"); });

//以下是线程2的Token,线程1取消时进行时的操作
cts1.Token.Register(() => { Console.WriteLine("线程2取消时进行的操作0"); });
cts1.Token.Register(() => { Console.WriteLine("线程2取消时进行的操作1"); });
cts1.Token.Register(() => { Console.WriteLine("线程2取消时进行的操作2"); });

//以下是线程3的Token,线程1取消时进行时的操作
cts2.Token.Register(() => { Console.WriteLine("线程3取消时进行的操作0"); });
cts2.Token.Register(() => { Console.WriteLine("线程3取消时进行的操作1"); });
cts2.Token.Register(() => { Console.WriteLine("线程3取消时进行的操作2"); });

Console.WriteLine("线程将在10秒后取消");
//定时取消一个线程的执行,将于10秒后停止执行
//cts.CancelAfter(10000);

Console.WriteLine("回车取消当前线程");
Console.ReadLine();
//立即取消一个线程的执行,取消线程1
cts.Cancel();
//立即取消一个线程的执行取消线程2
cts1.Cancel();

Console.ReadLine();
//ThreadPool的QueueUserWorkItem的异步操作,有许多限制,最大的问题就是没有内建的机制,你不知道这个线程什么时候完成,
//也没有操作完成时的返回值,为了克服这些限制,使用System.Threading.Tasks中任务同样可以做异步造作.
#endregion
}

//实例四 System.Threading.Tasks任务的使用
static void Main(string[] args)
{
#region System.Threading.Tasks任务的使用
//实例四 System.Threading.Tasks任务的使用
ThreadPool.QueueUserWorkItem(SecondMethod);// 使用QueueUserWorkItem,线程池
new Task(SecondMethod, "参数").Start();// 有参数,可以传递一个参数
new Task(GetContextData, TaskCreationOptions.None).Start();//无参数
Task.Factory.StartNew(GetContextData);// 创建并且启动线程(不带参数)
Task.Factory.StartNew(SecondMethod, "");// 创建并且启动线程(带参数)
Task.Run(() => { Console.WriteLine(" Task.Run() ..."); });//创建并且启动线程(不能带参数)
Console.ReadLine();

#endregion
}
public static void SecondMethod(Object state)
{
Console.WriteLine("执行完FirstMethod...");
}
public static void GetContextData()
{
Console.WriteLine("This is GetContextData");
}

//等待任务完成并且获取结果
static void Main(string[] args)
{
#region 等待任务完成并且获取结果
// 有返回值,只能返回一个参数
Task<int> task = Task<int>.Factory.StartNew(x =>
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine("---{0}", i);
}
return 111;
}, 100000);

// 无返回值
Task task1 = Task.Factory.StartNew(x =>
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine("---{0}", i);
}
return 111;
}, 100000);
//wait()会阻塞线程,等待task完成
task.Wait();
//WaitAll()会阻塞线程,等待task,task1完成
Task.WaitAll(task, task1);

//WaitAny()会阻塞线程,等待task,task1任意一个完成
Task.WaitAny(task, task1);
// 只有task完成才能执行下面的方法
Console.WriteLine("等待执行完成,执行结果{0}", task.Result);
Console.ReadLine();
#endregion
}

// 任务完成时启动新的任务
static void Main(string[] args)
{
#region 任务完成时启动新的任务
//使用wait等待任务完成的方式会阻塞线程,这样不但增加了系统资源的浪费,也不利于延展性和伸缩性
//以下的方式可以解决上面存在的问题
// 实例展示
// 创建一个新的任务
// 都是 针对 task是否完成或者异常
Task<int> task = Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine($"{i}");

}
Console.WriteLine("第一个任务开始:" + $"当前线程的Id为:{Thread.CurrentThread.ManagedThreadId}");
return 10;
});
//创建另一个新的任务,等待上面的任务完成后,将执行的结果展示出来
task.ContinueWith((x) =>
{
Console.WriteLine("执行的结果为:" + $"{x.Result}" + $"当前线程的Id为:{Thread.CurrentThread.ManagedThreadId}" + " 参数类型为:TaskContinuationOptions.None");
}, TaskContinuationOptions.None);// 默认
Console.ReadLine();

task.ContinueWith((x) =>
{
Console.WriteLine("执行的结果为:" + $"{x.Result}" + $"当前线程的Id为:{Thread.CurrentThread.ManagedThreadId}" + " 参数类型为:TaskContinuationOptions.OnlyOnRanToCompletion");

}, TaskContinuationOptions.OnlyOnRanToCompletion);//指示只有前面的任务完成
Console.ReadLine();

task.ContinueWith((x) =>
{
Console.WriteLine("执行的结果为:" + $"{x.Result}" + $"当前线程的Id为:{Thread.CurrentThread.ManagedThreadId}" + " 参数类型为:TaskContinuationOptions.NotOnRanToCompletion");
}, TaskContinuationOptions.NotOnRanToCompletion);//指示前面的任未完成
Console.ReadLine();

task.ContinueWith((x) =>
{
Console.WriteLine("执行的结果为:" + $"{x.Result}" + $"当前线程的Id为:{Thread.CurrentThread.ManagedThreadId}" + " 参数类型为:TaskContinuationOptions.OnlyOnCanceled");

}, TaskContinuationOptions.OnlyOnCanceled);//指示只有任务失败等价于 = OnlyOnFaulted||NotOnRanToCompletion
Console.ReadLine();

task.ContinueWith((x) =>
{
Console.WriteLine("执行的结果为:" + $"{x.Result}" + $"当前线程的Id为:{Thread.CurrentThread.ManagedThreadId}" + " 参数类型为:TaskContinuationOptions.OnlyOnFaulted");
}, TaskContinuationOptions.OnlyOnFaulted);//之前任务执行失败
Console.ReadLine();

task.ContinueWith((x) =>
{
Console.WriteLine("执行的结果为:" + $"{x.Result}" + $"当前线程的Id为:{Thread.CurrentThread.ManagedThreadId}" + " 参数类型为:TaskContinuationOptions.ExecuteSynchronously");
}, TaskContinuationOptions.ExecuteSynchronously);//指示这个任务在第一个任务上完成
Console.ReadLine();

task.ContinueWith((x) =>
{
Console.WriteLine("执行的结果为:" + $"{x.Result}" + $"当前线程的Id为:{Thread.CurrentThread.ManagedThreadId}" + " 参数类型为:TaskContinuationOptions.PreferFairness");
}, TaskContinuationOptions.PreferFairness);//指示希望这个任务尽快的执行
Console.ReadLine();
#endregion
}

//任务可以启动多个子任务
static void Main(string[] args)
{
#region 任务可以启动多个子任务
// 实例
// 创建一个父任务
Task<int[]> parentTasks = new Task<int[]>(() =>
{
//定义一个子任务子任务返回结果的数组,其中子任务都是异步执行
int[] result = new int[3];
//创建三个子任务,并且关联父任务
new Task(() =>
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine($"任务一{i}");
}
result[0] = 1;
}, TaskCreationOptions.AttachedToParent).Start();

new Task(() =>
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
Console.WriteLine($"任务二{i}");
}
result[1] = 2;
}, TaskCreationOptions.AttachedToParent).Start();

new Task(() =>
{
for (int i = 0; i < 2; i++)
{
Thread.Sleep(1000);
Console.WriteLine($"任务三{i}");
}
result[2] = 3;
}, TaskCreationOptions.AttachedToParent).Start();
return result;
});
//父任务完成之后子任务的返回结果
parentTasks.ContinueWith((x) =>
{
//遍历结果
Array.ForEach(parentTasks.Result, (s) =>
{
Console.WriteLine($"结果为{s}");
});
});
// 启动所有的子任务
parentTasks.Start();
Console.ReadLine();
#endregion
}

//任务工厂的使用
static void Main(string[] args)
{
#region 任务工厂的使用
//实例
// 创建一个相同配置的工厂对象
CancellationTokenSource cts = new CancellationTokenSource();
//创建一个统一配置的的任务工厂有返回值
TaskFactory<int> tf = new TaskFactory<int>(
cts.Token,
TaskCreationOptions.AttachedToParent,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default
);
//创建三个子任务
var childrensTask = new[]
{
tf.StartNew(() => {
Console.WriteLine("子任务一");
return 1;
},cts.Token),
tf.StartNew(() =>
{
Console.WriteLine("子任务一");
return 55;
}),
tf.StartNew(() =>
{
Console.WriteLine("子任三");
return 2;
})
};

//只要有一个任务执行失败,立即取消其他的所有任务
for (int task = 0; task < childrensTask.Length; task++)
{
childrensTask[task].ContinueWith((x) =>
{
cts.Cancel();
}, TaskContinuationOptions.OnlyOnFaulted);

}
// 当所有的任务都完成
tf.ContinueWhenAll(childrensTask, t =>
{
return childrensTask.Where(x => !x.IsCanceled || !x.IsFaulted).Max(tt => tt.Result);
}, CancellationToken.None).ContinueWith(h =>
{

Console.WriteLine($"最大值为{h.Result}");
});

Console.ReadLine();
#endregion
}

//任务调度器 常用
#region 任务调度器 常用
//实例
//任务调度器非常灵活
//默认情况下FcL提供了两个任务调度器
//1 线程池任务调度器 (thread pool task scheduler)
//2 同步上下文任务调度器(synchroizition context task scheduler)
//所有的应用程序都是有的是线程池任务调度器
//带界面的调度池使用上下文调度器,比如Winforms WPF
TaskScheduler taskScheduler = TaskScheduler.Default;
//以下任务调度器只允许不超过N个任务同时执行
LimitedConcurrencyLevelTaskScheduler limitedConcurrencyLevelTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(2);//只允许2个任务

//以下四个任务 两两执行
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine($"任务一{i}");
}
}, CancellationToken.None, TaskCreationOptions.None, limitedConcurrencyLevelTaskScheduler);
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine($"任务二{i}");
}
}, CancellationToken.None, TaskCreationOptions.None, limitedConcurrencyLevelTaskScheduler);
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine($"任务三{i}");
}
}, CancellationToken.None, TaskCreationOptions.None, limitedConcurrencyLevelTaskScheduler);
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine($"任务四{i}");
}
}, CancellationToken.None, TaskCreationOptions.None, limitedConcurrencyLevelTaskScheduler);

Console.ReadLine();
Console.WriteLine($"{limitedConcurrencyLevelTaskScheduler.CurrentCount}");
Console.ReadLine();
#endregion
// 限制线程数的调度器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace 多线程的学习
{
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
/// <summary>Whether the current thread is processing work items.</summary>
[ThreadStatic]
private static bool _currentThreadIsProcessingItems;
/// <summary>The list of tasks to be executed.</summary>
private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)
/// <summary>The maximum concurrency level allowed by this scheduler.</summary>
private readonly int _maxDegreeOfParallelism;
/// <summary>Whether the scheduler is currently processing work items.</summary>
private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks)

/// <summary>
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the
/// specified degree of parallelism.
/// </summary>
/// <param name="maxDegreeOfParallelism">The maximum degree of parallelism provided by this scheduler.</param>
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
_maxDegreeOfParallelism = maxDegreeOfParallelism;
}

/// <summary>
/// current executing number;
/// </summary>
public int CurrentCount { get; set; }

/// <summary>Queues a task to the scheduler.</summary>
/// <param name="task">The task to be queued.</param>
protected sealed override void QueueTask(Task task)
{
// Add the task to the list of tasks to be processed. If there aren't enough
// delegates currently queued or running to process tasks, schedule another.
lock (_tasks)
{
Console.WriteLine("Task Count : {0} ", _tasks.Count);
_tasks.AddLast(task);
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
{
++_delegatesQueuedOrRunning;
NotifyThreadPoolOfPendingWork();
}
}
}
int executingCount = 0;
private static object executeLock = new object();
/// <summary>
/// Informs the ThreadPool that there's work to be executed for this scheduler.
/// </summary>
private void NotifyThreadPoolOfPendingWork()
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
// Note that the current thread is now processing work items.
// This is necessary to enable inlining of tasks into this thread.
_currentThreadIsProcessingItems = true;
try
{
// Process all available items in the queue.
while (true)
{
Task item;
lock (_tasks)
{
// When there are no more items to be processed,
// note that we're done processing, and get out.
if (_tasks.Count == 0)
{
--_delegatesQueuedOrRunning;

break;
}

// Get the next item from the queue
item = _tasks.First.Value;
_tasks.RemoveFirst();
}
// Execute the task we pulled out of the queue
base.TryExecuteTask(item);
}
}
// We're done processing items on the current thread
finally { _currentThreadIsProcessingItems = false; }
}, null);
}

/// <summary>Attempts to execute the specified task on the current thread.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued"></param>
/// <returns>Whether the task could be executed on the current thread.</returns>
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{

// If this thread isn't already processing a task, we don't support inlining
if (!_currentThreadIsProcessingItems) return false;

// If the task was previously queued, remove it from the queue
if (taskWasPreviouslyQueued) TryDequeue(task);

// Try to run the task.
return base.TryExecuteTask(task);
}

/// <summary>Attempts to remove a previously scheduled task from the scheduler.</summary>
/// <param name="task">The task to be removed.</param>
/// <returns>Whether the task could be found and removed.</returns>
protected sealed override bool TryDequeue(Task task)
{
lock (_tasks) return _tasks.Remove(task);
}

/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }

/// <summary>Gets an enumerable of the tasks currently scheduled on this scheduler.</summary>
/// <returns>An enumerable of the tasks currently scheduled.</returns>
protected sealed override IEnumerable<Task> GetScheduledTasks()
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_tasks, ref lockTaken);
if (lockTaken) return _tasks.ToArray();
else throw new NotSupportedException();
}
finally
{
if (lockTaken) Monitor.Exit(_tasks);
}
}

}
}

//Paraller的静态For 和ForEach 和 Invoke
static void Main(string[] args)
{
#region Paraller的静态For 和ForEach 和 Invoke

Task.Factory.StartNew(() =>
{
//进程下线程
while (true)
{
Thread.Sleep(1000);
Process current = Process.GetCurrentProcess();
ProcessThreadCollection allThreads = current.Threads;
Console.WriteLine($"活动总线程{allThreads.Count}");
}

});
//这个地方创建100000个线程Cpu使用率为100%
//集合较小的可以使用这个
//MaxDegreeOfParallelism 这个参数可以限制最大并发数目
Parallel.For(0, 100000, new ParallelOptions() { MaxDegreeOfParallelism = 10, CancellationToken = new CancellationToken() }, i =>
{
Thread.Sleep(5000);
Console.WriteLine($"当前线程{Thread.CurrentThread.ManagedThreadId}");

});
int[] s = new int[] { 1, 2, 3 };
Parallel.ForEach(s, new ParallelOptions { MaxDegreeOfParallelism = 10 }, x => { });
//并行执行不同的方法
Parallel.Invoke(new ParallelOptions() { MaxDegreeOfParallelism = 10 },
() => { },
() => { },
//....
() => { }

);
Console.ReadLine();

#endregion
}

//执行定时计算限制操作 定时程序
static void Main(string[] args)
{
#region 执行定时计算限制操作 定时程序
_time = new Timer(ChangeStstus, null, Timeout.Infinite, Timeout.Infinite);
// 设置为Timeout.Infinite 等待ChangeStstus方法完成在开始下一次回调
_time.Change(0, Timeout.Infinite);
// 以下不会等待ChangeStstus方法完成,就会开始第二次,一般不采用
// _time.Change(0, 2000);


// 除了以上方法我们还可以使用线程来做定时器
ChnangeStatus();
Console.WriteLine($"将在两秒后继续开始");
Console.ReadLine();

#endregion
}
//声明一个Time对象
public static Timer _time;
//这个方法签名必须和Callback的签名保持一致
public static void ChangeStstus(Object state)
{
Console.WriteLine($"将在两秒后继续开始");
// Do other work
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine($"{i}");
}

//将在2秒后继续触发回调方法,这里不写不会再触发,一定要保证让Time对象处于存活的状态
_time.Change(2000, Timeout.Infinite);
}
// 线程做定时
public static async void ChnangeStatus()
{
while(true)
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(2000);
Console.WriteLine($"执行次数中{i}");
}
//在不会阻塞线程的前提下延迟2秒,在while循环中2秒执行一次,会等待上面的work完成,2秒后开始下一次
await Task.Delay(2000);
}
}

//获取同步上下文任务调度器
//带界面的调度池使用上下文调度器,比如Winforms WPF
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 同步上下文任务调度器
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//获取同步上下文任务调度器
taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
TaskScheduler taskScheduler;
private void button1_Click(object sender, EventArgs e)
{

}

private void button2_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
//这里不是用同步上下文调度池,需要用委托来更新ui线程
this.Invoke(new Action(() =>
{
button1.Text = "线程一";
}));

}, CancellationToken.None);
}

private void button3_Click(object sender, EventArgs e)
{
//TaskFactory tf = new TaskFactory(
// CancellationToken.None,
// TaskCreationOptions.PreferFairness,
// TaskContinuationOptions.ExecuteSynchronously,
// taskScheduler
// );
//tf.StartNew(() => {
// button1.Text = "线程er";

//});
// 使用调度池,直接在ui线程上更新
Task.Factory.StartNew(() =>
{
button1.Text = "线程er";
}, CancellationToken.None, TaskCreationOptions.None, taskScheduler);
}
}
}

  

posted @ 2020-04-03 15:56  谁说程序猿很猥琐  阅读(208)  评论(0编辑  收藏  举报