如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时对一个变量执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
lock的目的是防止多线程执行的时候出现并发操作问题,加上lock的引用类型的对象,在其锁定的区域内,在一个时刻只允许一个线程操作。
lock只能锁定一个引用类型变量,也就是锁定一个地址
class Program
{
static void Main(string[] args)
{
ThreadA t = new ThreadA();
#region Thread
ThreadA.obj.i = 10;
Thread th1 = new Thread(new ThreadStart(t.hhh));
th1.Name = "Th1";
th1.Start();
Thread th2 = new Thread(new ThreadStart(t.hhh));
th2.Name = "Th2";
th2.Start();
#endregion
#region Task
ThreadA.obj.i = 10;
Task t1 = new Task(t.hhh);
t1.Start();
Task t2 = new Task(t.hhh);
t2.Start();
#endregion
Console.WriteLine("Hello World!");
Console.WriteLine();
Console.ReadKey();
}
}
class ThreadA
{
public static IntI obj = new IntI();
public void hhh()
{
lock (obj)
{
for (int i = 0; i < 7; i++)
{
Thread.Sleep(500);
if (obj.i > 0)
{
obj.i--;
Console.WriteLine("当前线程名: " + Thread.CurrentThread.ManagedThreadId+ ",obj.i= " + obj.i);
}
}
}
}
}
class IntI
{
public int i;
}
加锁和不加锁运行的结果有区别 :
加锁后:i的值会一个个递减,不会出现跳跃,不会出现重复输出,一直到0值;
不加锁:i的值输出会出现跳跃,不连续递减,可能还会出现-1值输出;
原因:加锁后,一个时刻只能有一个线程执行被锁区域的代码,两个线程都是有先后顺序执行的,所以不会出现间断输出。
Task是用来实现多线程的类,在以前当版本中已经有了Thread及ThreadPool,为什么还要提出Task类呢,这是因为直接操作Thread及ThreadPool,向线程中传递参数,获取线程的返回值及线程当启停都非常的麻烦,所以微软的工程师对Thread进行了再封装,这就是Task,可以这么说Task是架构在Thread之上的,
所以多线程时Task是我们的首选。
Task类和Task<TResult>类,后者是前者的泛型版本。TResult类型为Task所调用方法的返回值。
主要区别在于Task构造函数接受的参数是Action委托,而Task<TResult>接受的是Func<TResult>委托
- Task的声明
Task的声明有两种方式:
a,通过new 的方式来声明
Task objTask = new Task();
b.通过Task.Factory.StartNew的方式来声明
Task.Factory.StartNew(MyMethod);
这两种声明方式的区别,第一种声明方式开启线程必须使用objTask.Start(),而通过Task.Factory.StartNew的方式则不用。
class Program
{
static void Main(string[] args)
{
Console.WriteLine("调用主线程");
//Task objTask = new Task(() => Console.WriteLine("Task1"));
//objTask.Start();
//new Task(() => Console.WriteLine("Task2")).Start();
//Task t1 = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine("Task3"); });
//t1.Wait();
//Task t2 = Task.Run<string>(() => "wwmin");
//bool b2 = t2.Wait(2000);
//Thread.Sleep(4000);
#region getWaiter and continueWith
Task<int> TaskInt1 = Task.Run<int>(() =>
{
Thread.Sleep(2000);
return Enumerable.Range(1, 100).Sum();
});
var awaiter = TaskInt1.GetAwaiter();
awaiter.OnCompleted(() =>
{
Console.WriteLine("TaskInt1 finished");
int result = awaiter.GetResult();
Console.WriteLine(result);
});
TaskInt1.ContinueWith(antecedent =>
{
Console.WriteLine(antecedent.Result);
Console.WriteLine("Running continue Task");
});
#endregion
Task<string> s = TestAsync();
Console.WriteLine(s.Result);
Console.WriteLine("Hello World!");
Console.ReadKey();
}
static async Task<string> TestAsync()
{
Console.WriteLine("运行task之前" + Thread.CurrentThread.ManagedThreadId);
Task<string> t = Task.Run<string>(() =>
{
Console.WriteLine("运行Task" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
return "我是测试线程";
});
Console.WriteLine("运行Task之后" + Thread.CurrentThread.ManagedThreadId);
var result = await t;
return result;
}
}
延迟任务
Task.Delay()方法是相当于异步的Thread.Sleep();
要获得返回值,就要用到Task的泛型版本了。 说到Task的返回值就不得不说await和async关键字了。
当函数使用async标记后,返回值必须为void,Task,Task<T>,当返回值为Task<T>时,函数内部只需要返回T类型,编译器会自动包装成Task<T>类型
await关键字必须在具有async标记的函数内使用。
(1) 在async标识的方法体里面,如果没有await关键字的出现,那么这种方法和调用普通的方法没什么区别(就是说async和await是成对出现的,没有await的async是没有意义的)
(2)在async标识的方法体里面,在await关键字出现之前,还是主线程顺序调用的,直到await关键字的出现才会出现线程阻塞。
(3)await关键字可以理解为等待方法执行完毕,除了可以标记有async关键字的方法外,还能标记Task对象,表示等待该线程执行完毕。所以await关键字并不是针对于async的方法,而是针对async方法所返回给我们的Task。
延续任务,就是说在任务执行完成之后继续执行任务,有两种方法
第一种,使用一种是使用GetAwaiter方法。GetAwaiter方法返回一个TaskAwaiter结构,该结构有一个OnCompleted事件,只需对OnCompleted事件赋值,即可在完成后调用该事件。
网友评论