闵行网站搭建哪里有,网站后台 模板,网站建设优化服务资讯,社交网站 建站阅读目录 代码下载一、线程的创建和开始二、传递数据给一个线程三、命名线程四、前台线程和后台线程五、线程优先级六、异常处理代码下载
Thread_博客园_cnblogs_jackson0714.zip
第一篇~第三篇的代码示例#xff1a; 源码地址#xff1a;https://github.com/Jackson0714/T… 阅读目录 代码下载一、线程的创建和开始二、传递数据给一个线程三、命名线程四、前台线程和后台线程五、线程优先级六、异常处理代码下载
Thread_博客园_cnblogs_jackson0714.zip
第一篇~第三篇的代码示例 源码地址https://github.com/Jackson0714/Threads 回到顶部
一、线程的创建和开始
在第一篇的介绍中线程使用Thread 类的构造函数来创建通过传给一个ThreadStart 委托来实现线程在哪里开始执行。下面是ThreadStart的定义
// Summary:
// Represents the method that executes on a System.Threading.Thread.
[ComVisible(true)]
public delegate void ThreadStart();
调用一个Start方法然后设置它开始运行。线程会一直运行直到这个方法返回然后这个线程结束。
下面是一个例子使用扩展C#语法创建一个ThreadStart委托2.1_ThreadStart 1 class ThreadTest2 {3 static void Main()4 {5 Thread t new Thread(new ThreadStart(Go));6 t.Start(); 7 Go();8 Console.ReadKey();9 }
10 static void Go()
11 {
12 Console.WriteLine(hello!);
13 }
14 } 在这个例子中thread t执行Go(),基本上与主线同时程调用Go()方法结果是打印出两个时间接近的hello。
一个线程可以被方便的创建通过指定一个方法组然后由C#推断出ThreadStart委托2.2_Thread 1 class Program2 {3 static void Main(string[] args)4 {5 Thread t new Thread(Go);6 t.Start();7 Go();8 Console.ReadKey();9 }
10
11 static void Go()
12 {
13 Console.WriteLine(Go);
14 }
15 } 另外一种更简单的方式是使用lambda表达式或者匿名方法2.3_LambaExpression static void Main(string[] args)
{Thread t new Thread(()Console.WriteLine(Go));t.Start();Console.ReadKey();
} 回到顶部
二、传递数据给一个线程
1.利用Lambda传递一个数据
传递参数给线程的目标方法的最简单的方法是执行一个lambda表达式该表达式调用一个方法并传递期望的参数给这个方法。
2.4_PassingDataToAThread static void Main(string[] args)
{Thread t new Thread(() Print(A));t.Start();Console.ReadKey();
}static void Print(string message)
{Console.WriteLine(message);
} 2.传递多个参数
通过这种方式你可以传递任意数量的参数给这个方法。你甚至可以将整个实现包装在一个多语句的lambda中
2.5_PassingDataToAThread
new Thread(()
{Console.WriteLine(a);Console.WriteLine(b);
}).Start();
你也可以简单的在C# 2.0里面那样使用匿名方法做同样的事
new Thread(delegate()
{Console.WriteLine(a);Console.WriteLine(b);
}).Start();
3.利用Thread.Start传递参数
另外一种方式是传递一个参数给Thread的Start方法
2.6_PassingDataToAThread_ThreadStart
static void Main(string[] args)
{Thread t new Thread(Print);t.Start(A);Console.ReadKey();
}
static void Print(object messageObj)
{string message (string)messageObj;//必须进行转换Console.WriteLine(message);
} 这种方式能够工作是因为Thread的构造函数是重载的接受下面两种中的任意一种委托 // Summary:
// Represents the method that executes on a System.Threading.Thread.
[ComVisible(true)]
public delegate void ThreadStart();// Summary:
// Represents the method that executes on a System.Threading.Thread.
//
// Parameters:
// obj:
// An object that contains data for the thread procedure.
[ComVisible(false)]
public delegate void ParameterizedThreadStart(object obj); 这个ParameterizedThreadStart的只允许接收一个参数。而且因为它的类型是object所以通常需要转换。
4.Lambda表达式和捕获变量
由我们上面看到的例子可以知道一个lambda式在传递数据给线程是最用的。然而你必须非常小心在开始线程后意外修改捕获变量因为这些变量是共享的。比如下面的
2.7_LbdaExpressionsAndCapturedVariables
for(int i 0;i10;i)
{new Thread(() Console.Write(i)).Start();
}
这个输出是不确定的下面是一种典型的情况 这里的问题是变量i在for循环执行时指向同一个内存地址。因此每一个线程调用Console.Write时i的值有可能在这个线程运行时改变。
解决方案是使用一个临时变量
2.8_LambdaExpressionsAndCapturedVariables_Solution
for (int i 0; i 10; i)
{int temp i;new Thread(() Console.Write(temp)).Start();
}
变量temp在每个循环迭代中位于不同的内存块。因此每一个线程捕获到了不同的内存位置而且没有问题。我们可以解释在之前的代码中的问题
2.9_PassingData_TemporaryVariable string text A;
Thread a new Thread(() Console.WriteLine(text));text B;
Thread b new Thread(() Console.WriteLine(text));a.Start();
b.Start(); 因为两个lambda表达式捕获同样的text的值所以B被打印出两次。 回到顶部
三、命名线程
每一个线程有一个Name属性你可以方便用来debugging.当线程显示在Visual Statudio里面的Threads Window和Debug Loaction toolbar的时候线程的Name属性是特别有用的。你可以只设置线程的名字一次之后尝试改变它将会抛出异常信息。
静态的Thread.CurrentThread属性代表当前执行的线程。
在下面的例子2.10_NamingThread中我们设置了主线程的名字 static void Main(string[] args)
{Thread.CurrentThread.Name Main Thread;Thread t new Thread(Go);t.Name Worker Thread;t.Start();Go();Console.ReadKey();
}
static void Go()
{Console.WriteLine(Go! The current thread is {0}, Thread.CurrentThread.Name);
} 回到顶部
四、前台线程和后台线程
默认情况下你自己显示创建的线程是前台线程。前台线程保持这个应用程序一直存活只要其中任意一个正在运行而后台线程不是这样的。一旦所有的前台线程完成这个应用程序就结束了 任何正在运行的后台线程立刻终止。
一个线程前台/后台的状态跟它的优先级和配置的执行时间没有关联。
你可以使用线程的IsBackgroud属性查询或改变一个线程的后台状态。
下面是例子2.11_PriorityTest static void Main(string[] args)
{Thread t new Thread(() Console.ReadKey());if (args.Length 0)//如果Main方法没有传入参数{//设置线程为后台线程等待用户输入。//因为主线程在t.Start()执行之后就会终止//所以后台线程t会在主线程退出之后立即终止应用程序就会结束。t.IsBackground true;}t.Start();
} 如果程序调用的时候传入了参数则创建的线程为前台线程然后等待用户输入。
同时如果主线程退出应用程序将不会退出因为前台线程t没有退出。
另一方面如果main方法传入了参数则创建的线程设置为后台线程。当主线程退出时应用程序立即退出。
当一个进程以这种方式终止则任何后台线程执行栈里面的finally 语句块将会被规避。
如果你的线程使用finally(or using)语句块去执行如释放资源或者删除临时文件的清理工作这将是一个问题。为了避免这个你可以显示地等待后台线程退出应用程序。
这里有两种实现方式
如果你自己创建了这个线程可以在这个线程上调用Join方法。如果你使用线程池可以使用一个事件去等待处理这个线程。
在这两种情况下你需要指定一个timeout因此可以结束一个由于某些原因拒绝完成的线程。这是你的备选退出策略在最后你想要你的应用程序关闭不需要用户从任务管理器中删除。
如果用户使用任务管理器强制结束一个.NET进程所有的线程像是后台线程一样终止。这个是观察到的行为所以会因为CLR和操作系统的版本而不同。
前台线程不需要这样对待但是你必须小心避免可能造成线程不能结束的bugs。造成应用程序不能正确地退出的一个通常的原因是有激活的前台线程还存活在。
回到顶部
五、线程优先级
一个线程的优先级决定了在操作系统中它可以得到多少相对其他线程的执行时间下面是线程优先级的等级 // Summary:
// Specifies the scheduling priority of a System.Threading.Thread.
[Serializable]
[ComVisible(true)]
public enum ThreadPriority
{Lowest 0,BelowNormal 1,Normal 2,AboveNormal 3,Highest 4,
} 当多线程同时是激活的线程优先级是很重要的。
注意提高线程优先级时需要非常小心这将可能导致其他线程对资源访问的饥饿状态的问题。
当提升一个线程的优先级时不会使它执行实时工作因为它被应用程序的进程优先级限制了。为了执行实时工作你也必须通过使用System.Diagnostices的Process类来提升进程的优先级
using (Process p Process.GetCurrentProcess())
{p.PriorityClass ProcessPriorityClass.High;
}
ProcessPriorityClass.High事实上是优先级最高的一档实时。设置一个进程优先级到实时状态将会导致其他线程无法获得CPU时间片。如果你的应用程序意外地进入一个无限循环的状态你甚至会发现操作被锁住了只有电源键能够拯救你了。针对这个原因High通常对于实时应用程序是最好的选择。
如果你的实时应用程序有一个用户界面提高程序的优先级将会使刷新界面占用昂贵的CPU的时间且会使整个系统变得运行缓慢尤其是UI很复杂的时候。降低主线程优先级且提升进程的优先级来确保实时线程不会被界面重绘所抢占但是不会解决其他进程对CPU访问缺乏的问题因为操作系统整体上会一直分配不成比例的资源给进程。一个理想的解决方案是让实时线程和用户界面用不同的优先级运行在不同的进程中通过远程和内存映射文件来通信。即使提高了进程优先级在托管环境中处理硬实时系统需求还是对适用性有限制。此外潜藏的问题会被自动垃圾回收引进操作系统会遇到新的挑战即使是非托管代码使用专用硬件或者特殊的实时平台那将被最好的解决。
回到顶部
六、异常处理
在任何try/catch/finally 语句块作用域内创建的线程当这个线程开始时这个线程和语句块是没有关联的。
思考下面的程序 参考例子2.12_ExceptionHandling static void Main(string[] args)
{try{new Thread(Go).Start();}catch(Exception ex){Console.WriteLine(Exception);}Console.ReadKey();
}
static void Go()
{throw null;
} try/catch 声明在这个例子中是无效的而且新创建的线程将会被一个未处理的NullReferenceException所阻断。当你考虑每一个线程有一个单独的执行路径这种行为是说得通的。
改进方法是将exception handler移到Go()的方法中
参考例子2.13_ExceptionHandling_Remedy class Program
{static void Main(string[] args){new Thread(Go).Start();Console.ReadKey();}static void Go(){try{throw null;}catch (Exception ex){Console.WriteLine(ex.Message);}}
} 你需要在应用程序中的所有线程入口方法中添加一个exception handler 就像你在主线程中做的那样。一个未处理的线程会造成整个应用程序关闭而且会弹出一个不好看的窗口。
在写这个exception handling 语句块时你可能极少忽略这个问题典型情况是你可能会记录exception的详细信息然后可能显示一个窗口让用户去自动去提交这些信息到你的web server上。然后你可能会关掉这个应用程序-因为这个error毁坏了程序的状态。然后这样做的开销是用户可能会丢失他最近的工作比如打开的文档。
对于WPF和WinForm应用程序来说全局的exception handling 事件Application.DispatcherUnhandlerException 和Application.ThreadException只会检测到主UI线程上的抛出的异常。你还是必须手动处理线程的异常。
AppDomain.CurrentDomain.UnhandledException可以检测任何未处理的异常但是无法阻止应用程序之后关闭。
然而某些情形下你不需要在线程上处理异常因为.NET Framework为你做了这个。下面是没有提及的内容
Asynchronous delegates
BackgroudWorker
The Task Parallel Library(conditions apply)