C# Timer和多线程编程、委托、异步、Func/Action
一、C#Timers的区别
C#有3种Timer
1.System.Threading.Timer
2.System.Timers.Timer
3.System.Windows.Forms.Timer
主要区别:System.Threading.Timer和
System.Timers.Timer
是多线程的,只要时间到了,就会执行。哪怕前一次还没执行完,他还是会开个线程继续执行新的任务。
System.Windows.Forms.Timer
是单线程的,只有等前一次执行完了,才会执行第二次的任务。如果间隔5秒执行,如果第一次任务处理超过5秒,那么就会延后第二次任务。
- System.Timers.Timer它触发事件并执行的代码中一个或多个事件接收器按固定间隔。 类适用于作为基于服务器的使用或在多线程环境; 中的服务组件它没有用户界面并不是在运行时中可见。
- System.Threading.Timer其中按固定间隔在线程池线程上执行的单个回调方法。 当计时器实例化,并且不能更改定义的回调方法。 如System.Timers.Timer类,此类旨在为基于服务器或服务组件在多线程环境中使用; 它没有用户界面并不是在运行时中可见。
- System.Windows.Forms.Timer (仅在.NET framework 中),触发事件并在固定时间间隔的一个或多个事件接收器中执行代码的 Windows 窗体组件。 该组件没有用户界面,专供在单线程环境中;它在 UI 线程上执行。
使用方法参考:C#-Forms.Timer、Timers.Timer、Threading.Timer的比较和使用
C#实现一个简单的定时任务 System.Timers.Timer
最近用Timer踩了一个坑,分享一下避免别人继续踩 System.Timers.Timer 线程安全
C# 多线程九之Timer类 System.Threading.Timer 线程安全
c# 多线程之-- System.Threading Timer的使用
注:Timers.Timer可为同一回调方法配置多个定时器,第一次执行为声明之后一个间隔,Threading.Timer为相同方法设置定时器时,只要一个定时器使用了 Timeout.Infinite,会导致其他定时器也不能循环执行,可配置第一次执行的时间。
使用委托开启多线程(多线程深入)
1、用委托(Delegate)的BeginInvoke和EndInvoke方法操作线程
BeginInvoke方法可以使用线程异步地执行委托所指向的方法。然后通过EndInvoke方法获得方法的返回值(EndInvoke方法的返回值就是被调用方法的返回值),或是确定方法已经被成功调用。
class Program { private delegate int NewTaskDelegate(int ms); private static int newTask(int ms) { Console.WriteLine("任务开始"); Thread.Sleep(ms); Random random = new Random(); int n = random.Next(10000); Console.WriteLine("任务完成"); return n; } static void Main(string[] args) { NewTaskDelegate task = newTask; IAsyncResult asyncResult = task.BeginInvoke(2000, null, null); //EndInvoke方法将被阻塞2秒 int result = task.EndInvoke(asyncResult); Console.WriteLine(result); Console.Read(); } }
2、使用IAsyncResult.IsCompleted属性来判断异步调用是否完成。
class Program { private delegate int NewTaskDelegate(int ms); private static int newTask(int ms) { Console.WriteLine("任务开始"); Thread.Sleep(ms); Random random = new Random(); int n = random.Next(10000); Console.WriteLine("任务完成"); return n; } static void Main(string[] args) { NewTaskDelegate task = newTask; IAsyncResult asyncResult = task.BeginInvoke(2000, null, null); //等待异步执行完成 while (!asyncResult.IsCompleted) { Console.Write("*"); Thread.Sleep(100); } // 由于异步调用已经完成,因此, EndInvoke会立刻返回结果 int result = task.EndInvoke(asyncResult); Console.WriteLine(result); Console.Read(); } }
3、使用WaitOne方法等待异步方法执行完成
WaitOne的第一个参数表示要等待的毫秒数,在指定时间之内,WaitOne方法将一直等待,直到异步调用完成,并发出通知,WaitOne方法才返回true。当等待指定时间之后,异步调用仍未完成,WaitOne方法返回false,如果指定时间为0,表示不等待,如果为-1,表示永远等待,直到异步调用完成。
class Program { private delegate int NewTaskDelegate(int ms); private static int newTask(int ms) { Console.WriteLine("任务开始"); Thread.Sleep(ms); Random random = new Random(); int n = random.Next(10000); Console.WriteLine("任务完成"); return n; } static void Main(string[] args) { NewTaskDelegate task = newTask; IAsyncResult asyncResult = task.BeginInvoke(2000, null, null); //等待异步执行完成 while (!asyncResult.AsyncWaitHandle.WaitOne(100, false)) { Console.Write("*"); } int result = task.EndInvoke(asyncResult); Console.WriteLine(result); Console.Read(); } }
4、使用回调方式返回结果
要注意的是“my.BeginInvoke(3,300, MethodCompleted, my)”,BeginInvoke方法的参数传递方式:
前面一部分(3,300)是其委托本身的参数。
倒数第二个参数(MethodCompleted)是回调方法委托类型,他是回调方法的委托,此委托没有返回值,有一个IAsyncResult类型的参数,当method方法执行完后,系统会自动调用MethodCompleted方法。
最后一个参数(my)需要向MethodCompleted方法中传递一些值,一般可以传递被调用方法的委托,这个值可以使用IAsyncResult.AsyncState属性获得。
class Program { private delegate int MyMethod(int second, int millisecond); //线程执行方法 private static int method(int second, int millisecond) { Console.WriteLine("线程休眠" + (second * 1000 + millisecond) + "毫秒"); Thread.Sleep(second * 1000 + millisecond); Random random = new Random(); return random.Next(10000); } //回调方法 private static void MethodCompleted(IAsyncResult asyncResult) { if (asyncResult == null || asyncResult.AsyncState == null) { Console.WriteLine("回调失败!!!"); return; } int result = (asyncResult.AsyncState as MyMethod).EndInvoke(asyncResult); Console.WriteLine("任务完成,结果:" + result); } static void Main(string[] args) { MyMethod my = method; IAsyncResult asyncResult = my.BeginInvoke(3, 300, MethodCompleted, my); Console.WriteLine("任务开始"); Console.Read(); } }
5、其他组件的BeginXXX和EndXXX方法
在其他的.net组件中也有类似BeginInvoke和EndInvoke的方法,如System.Net.HttpWebRequest类的BeginGetResponse和EndGetResponse方法。其使用方法类似于委托类型的BeginInvoke和EndInvoke方法,例如
class Program { //回调函数 private static void requestCompleted(IAsyncResult asyncResult) { if (asyncResult == null || asyncResult.AsyncState == null) { Console.WriteLine("回调失败"); return; } HttpWebRequest hwr = asyncResult.AsyncState as HttpWebRequest; HttpWebResponse response = (HttpWebResponse)hwr.EndGetResponse(asyncResult); StreamReader sr = new StreamReader(response.GetResponseStream()); string str = sr.ReadToEnd(); Console.WriteLine("返回流长度:" + str.Length); } static void Main(string[] args) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://www.baidu.com"); //异步请求 IAsyncResult asyncResult = request.BeginGetResponse(requestCompleted, request); Console.WriteLine("任务开始"); Console.Read(); } }
三、委托、Func、Action
1、Func 用法 (封装方法,传入参数, 有返回值)
Func<in T1, in T2, ..., out TResult> (T1, T2, ...)
封装一个方法,该方法有 (0 /1/2/3 ... 16)个参数,且返回由 TResult 参数指定的值的类型。
2、Action 用法 (封装一个方法, 传入参数, 无返回值)
Action<T1, T2, T3, ...>(t1, t2, t3 ...)
封装一个方法, 该方法传入 (0/1/2 ...) 个参数, 且不返回值。
参考资料:c# 委托(Func、Action)
C#异步编程のTask模型返回值Task<TResult>应用
C#多线程和异步(二)——Task和async/await详解
WaitAll,主要是为了解决多个不太相关的操作同步执行的话,耗时较多,这个方法可以使得他们异步同时执行,然后当所有操作都完成以后,再去进行接下来的操作。
WhenAll其实跟WaitAll是为了实现一样的功能,只是在WaitAll基础上又做了一层包装,看代码就明白了
五、C#委托、事件、回调的区别
事件包括:事件发送者、事件接收者和事件处理程序。事件的发送者必须知道发送什么类型的事件,以及相关的事件参数。而事件的接收者必须了解其事件的处理方法必须使用的返回类型和参数。事件的发送者和事件的接收者都只关注事件而不是对方,这样对象就只需考虑自己,而不用考虑其他对象。当多个对象订阅同一个事件时,IDE(Integrated Development Environment)就会把它们的事件处理程序一个接一个地串起来关联到这个事件,并按照订阅顺序,先后执行。可以用+=增加事件处理函数。
委托delegate可以创建一个引用变量,但是并不是指向类的实例,而是指向一个类中的某个方法。
设计.NET事件的5步骤:
1):定义参数类型:从类型EventArgs派生出满足要求的事件参数类
2):定义事件处理者委托:与第一步相关该步一般被泛型委托取代了
3):定义事件成员:在自定义类中,由事件处理者委托定义一个或多个事件成员
4):触发事件:自定义类的引发事件方法中,通知所有事件订阅者
5):订阅事件:注册事件处理程序
回调(Callback Method):一个对象将一个方法的引用(方法的引用用委托保存)传入另一个对象,使得只有他能返回信息,这就是一个回调。回调方法是一种在操作或活动完成时由委托自动调用的方法。回调不同于事件,回调不能发布,不是类型,是一种设计模式,回调在两个类之间建立一种关系,其中一个对象会自动对另一个对象做出反应,回调一般是私有的,保存回调的类对于谁能访问这个回调有所控制,回调通常在对象的构造函数中设置。