C#Task学习
简介:
Task 对象是一种的中心思想基于任务的异步模式首次引入.NET Framework 4 中。 因为由执行工作Task对象通常以异步方式执行线程池线程上而不是以同步方式在主应用程序线程中,可以使用Status属性,并将IsCanceled, IsCompleted,和IsFaulted属性,以确定任务的状态。
一.Task的创建
1.创建Task类
(1)
Task task = new Task(() =>
{
Console.WriteLine("hello world!");
});
task.Start();
(2)
new Task(() =>
{
Console.WriteLine("hello world!");
}).Start();
(3)带参数
new Task(x =>
{
Console.WriteLine(x.ToString());
}, "hello world!").Start();
(4)带返回值
Task<string> t = new Task<string>(x =>
{
return x.ToString();
}, "hello world!");
t.Start();
Console.WriteLine(t.Result);
2.Task.Factory.StartNew
(1)
Task.Factory.StartNew(() =>
{
Console.WriteLine("hello world!");
});
(2)带参数
Task.Factory.StartNew(x =>
{
Console.WriteLine(x.ToString());
}, "hello world!");
(3)带返回值
Task<string> t = Task.Factory.StartNew<string>(() =>
{
return "hello world!";
});
Console.WriteLine(t.Result);
3.Task.Run
Task.Run(() =>
{
Console.WriteLine("hello world!");
});
4.TaskStatus
Created = 0, //该任务已初始化,但尚未被计划。
WaitingForActivation = 1, //该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。
WaitingToRun = 2,//该任务已被计划执行,但尚未开始执行。
Running = 3, //该任务正在运行,但尚未完成。
WaitingForChildrenToComplete = 4,//该任务已完成执行,正在隐式等待附加的子任务完成。
RanToCompletion = 5,//已成功完成执行的任务。
Canceled = 6, //该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态;或者在该任务开始执行之前,已向该任务的CancellationToken 发出了信号。 有关详细信息,请参阅任务取消。
Faulted = 7 //由于未处理异常的原因而完成的任务。
二. TaskCreationOptions
Task.Factory.StartNew和创建Task类可以带TaskCreationOptions参数而Task.Run不可以带
//
// 摘要:
// 指定应使用默认行为。
None = 0,
//
// 摘要:
// 提示 System.Threading.Tasks.TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
PreferFairness = 1,
//
// 摘要:
// 指定任务将是长时间运行的、粗粒度的操作,涉及比细化的系统更少、更大的组件。 它会向 System.Threading.Tasks.TaskScheduler
// 提示,过度订阅可能是合理的。 可以通过过度订阅创建比可用硬件线程数更多的线程。 它还将提示任务计划程序:该任务需要附加线程,以使任务不阻塞本地线程池队列中其他线程或工作项的向前推动。
LongRunning = 2,
//
// 摘要:
// 指定将任务附加到任务层次结构中的某个父级。 默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。 可以使用 System.Threading.Tasks.TaskContinuationOptions.AttachedToParent
// 选项以便将父任务和子任务同步。 请注意,如果使用 System.Threading.Tasks.TaskCreationOptions.DenyChildAttach
// 选项配置父任务,则子任务中的 System.Threading.Tasks.TaskCreationOptions.AttachedToParent 选项不起作用,并且子任务将作为分离的子任务执行。
// 有关详细信息,请参阅附加和分离的子任务。
AttachedToParent = 4,
//
// 摘要:
// 指定任何尝试作为附加的子任务执行(即,使用 System.Threading.Tasks.TaskCreationOptions.AttachedToParent
// 选项创建)的子任务都无法附加到父任务,会改成作为分离的子任务执行。 有关详细信息,请参阅附加和分离的子任务。
DenyChildAttach = 8,
//
// 摘要:
// 防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 System.Threading.Tasks.TaskScheduler.Default
// 当前计划程序。
HideScheduler = 16
1. LongRunning
任务是长时间任务,就需要用LongRunning,可能会创建一个非线程池线程来执行该任务,防止阻塞线程池队列中的其他线程
private static void fun8()
{
Task.Factory.StartNew(() =>
{
Console.WriteLine($"task1.线程 id {Thread.CurrentThread.ManagedThreadId}. 是否为线程池线程: {Thread.CurrentThread.IsThreadPoolThread}");
}); Task.Factory.StartNew(() =>
{
Console.WriteLine($"task2.线程 id {Thread.CurrentThread.ManagedThreadId}. 是否为线程池线程: {Thread.CurrentThread.IsThreadPoolThread}");
}, TaskCreationOptions.LongRunning);
}
运行结果:

2. 父子任务(AttachedToParent,DenyChildAttach)
AttachedToParent:将子任务附加到父任务上,表现为:附加到父任务上的所有子任务都结束,父任务才结束
DenyChildAttach:不允许子任务附加到父任务上
(1)子任务不附加到父任务
private static void fun5()
{
Task t = Task.Factory.StartNew(() =>
{
Console.WriteLine("parent"); Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("child");
});
});
t.ContinueWith(x =>
{
Console.WriteLine("parent over");
});
}
运行结果:

(2)子任务附加到父任务上,使用AttachedToParent
private static void fun6()
{
Task t = Task.Factory.StartNew(() =>
{
Console.WriteLine("parent"); Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("child");
},TaskCreationOptions.AttachedToParent);
});
t.ContinueWith(x =>
{
Console.WriteLine("parent over");
});
}
运行结果:

(3)拒绝子任务附加到父任务上,使用DenyChildAttach
private static void fun7()
{
Task t = Task.Factory.StartNew(() =>
{
Console.WriteLine("parent"); Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("child");
}, TaskCreationOptions.AttachedToParent);
}, TaskCreationOptions.DenyChildAttach);
t.ContinueWith(x =>
{
Console.WriteLine("parent over");
});
}
运行结果:

三.CancellationToken 取消任务
private static void fun4()
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken Token = cancellationTokenSource.Token;
//结束任务回调
Token.Register(() =>
{
Console.WriteLine("canceled");
}); Task.Factory.StartNew(() =>
{
try
{
while (true)
{
Console.WriteLine("hello world!");
Thread.Sleep(100);
Token.ThrowIfCancellationRequested();
}
}
catch (OperationCanceledException ocex)
{
}
catch (ObjectDisposedException odex)
{
}
catch (Exception ex)
{
} }, Token); Thread.Sleep(1000); cancellationTokenSource.Cancel();
}
执行结果:

当执行cancellationTokenSource.Cancel()后,任务进行到Token.ThrowIfCancellationRequested()代码后,throw出OperationCanceledException异常,才结束任务并执行cancel回调
四.方法
| Wait | 等待 System.Threading.Tasks.Task 完成执行过程 |
| WaitAll | 等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程 |
| WaitAny | 等待提供的任一 System.Threading.Tasks.Task 对象完成执行过程 |
| WhenAll | 创建一个任务,该任务将在所有 System.Threading.Tasks.Task 对象都完成时完成 |
| WhenAny | 任何提供的任务已完成时,创建将完成的任务 |
| ContinueWith | 创建一个在目标 System.Threading.Tasks.Task 完成时异步执行的延续任务 |
1 Wait
阻塞当前线程,等待任务执行完成
//等待 System.Threading.Tasks.Task 在指定的毫秒数内完成执行。
public bool Wait(int millisecondsTimeout);
//等待 System.Threading.Tasks.Task 完成执行过程。 如果在任务完成之前取消标记已取消,等待将终止。
public void Wait(CancellationToken cancellationToken);
//等待 System.Threading.Tasks.Task 完成执行过程。 如果在任务完成之前超时间隔结束或取消标记已取消,等待将终止。
public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken);
//等待 System.Threading.Tasks.Task 完成执行过程。
public void Wait();
//等待 System.Threading.Tasks.Task 在指定的时间间隔内完成执行。
public bool Wait(TimeSpan timeout);
使用方式:
t.Wait();//无限等待
t.Wait(100);//等待100ms
t.Wait(TimeSpan.FromMilliseconds(1500));//等待1500ms
例:
private static void fun9()
{
Task t = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("task");
});
t.Wait();
Console.WriteLine("main");
}
运行结果:

2.WaitAll
阻塞当前线程,等待所有任务执行完成
//等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程。
public static void WaitAll(params Task[] tasks);
//等待所有提供的可取消 System.Threading.Tasks.Task 对象在指定的时间间隔内完成执行。
public static bool WaitAll(Task[] tasks, TimeSpan timeout);
//等待所有提供的 System.Threading.Tasks.Task 在指定的毫秒数内完成执行。
public static bool WaitAll(Task[] tasks, int millisecondsTimeout);
//等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程(除非取消等待)。
public static void WaitAll(Task[] tasks, CancellationToken cancellationToken);
//等待提供的所有 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行,或等到取消等待。
public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);
使用:
private static void fun10()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
}); Task t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
}); //Task.WaitAll(t1, t2);
//Task.WaitAll(new Task[] { t1, t2 }, 200);
Task.WaitAll(new Task[] { t1, t2 }, TimeSpan.FromMilliseconds(200));
Console.WriteLine("main");
}
运行结果:

3.WaitAny
阻塞当前线程,等待任一任务执行完成
//等待提供的任一 System.Threading.Tasks.Task 对象完成执行过程。
public static int WaitAny(params Task[] tasks);
//等待任何提供的 System.Threading.Tasks.Task 对象在指定的时间间隔内完成执行。
public static int WaitAny(Task[] tasks, TimeSpan timeout);
//等待任何提供的 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行。
public static int WaitAny(Task[] tasks, int millisecondsTimeout);
//等待提供的任何 System.Threading.Tasks.Task 对象完成执行过程(除非取消等待)。
public static int WaitAny(Task[] tasks, CancellationToken cancellationToken);
//等待提供的任何 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行,或等到取消标记取消。
public static int WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);
使用:
private static void fun11()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
}); Task t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
}); //Task.WaitAny(t1, t2);
//Task.WaitAny(new Task[] { t1, t2 }, 200);
Task.WaitAny(new Task[] { t1, t2 }, TimeSpan.FromMilliseconds(200));
Console.WriteLine("main");
}
结果:

4.WhenAll
不阻塞当前线程,等待所有任务执行完成后,可以进行ContinueWith
//创建一个任务,该任务将在可枚举集合中的所有 System.Threading.Tasks.Task 对象都完成时完成。
public static Task WhenAll(IEnumerable<Task> tasks);
//创建一个任务,该任务将在数组中的所有 System.Threading.Tasks.Task 对象都完成时完成。
public static Task WhenAll(params Task[] tasks);
//创建一个任务,该任务将在可枚举集合中的所有 System.Threading.Tasks.Task`1 对象都完成时完成。
public static Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks);
//创建一个任务,该任务将在数组中的所有 System.Threading.Tasks.Task`1 对象都完成时完成。
public static Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks);
使用:
(1)不带返回值
public static Task WhenAll(params Task[] tasks);
private static void fun12()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
}); Task t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
}); Task.WhenAll(t1, t2).ContinueWith(t =>
{
Console.WriteLine("WhenAll");
}); Console.WriteLine("main");
}
执行结果:

(2)带返回值
public static Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks);
private static void fun13()
{
Task<string> t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
return "task1";
}); Task<string> t2 = Task.Factory.StartNew<string>(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
return "task2";
}); Task.WhenAll(new Task<string>[] { t1, t2 }).ContinueWith(t =>
{
string s = "WhenAll:";
foreach (string item in t.Result)
{
s += item;
}
Console.WriteLine(s);
}); Console.WriteLine("main");
}
执行结果:

5.WhenAny
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task> WhenAny(params Task[] tasks);
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task> WhenAny(IEnumerable<Task> tasks);
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks);
使用:
(1)不带参数
public static Task<Task> WhenAny(params Task[] tasks);
private static void fun14()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
}); Task t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
}); Task.WhenAny(t1, t2).ContinueWith(t =>
{
Console.WriteLine("WhenAny1");
}); Console.WriteLine("main");
}
运行结果:

(2)带参数:
public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
private static void fun15()
{
Task<string> t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
return "response task1";
}); Task<string> t2 = Task.Factory.StartNew<string>(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
return "response task2";
}); Task.WhenAny(new Task<string>[] { t1, t2 }).ContinueWith(t =>
{
t.Result.ContinueWith(tt =>
{
Console.WriteLine(tt.Result);
});
}); Console.WriteLine("main");
}
运行结果:

6.ContinueWith
相当于回调
6.1使用
(1)使用lambda表达式方式
private static void fun1()
{
Console.WriteLine("start");
Task<string> task1 = new Task<string>(() =>
{
Console.WriteLine("task0");
return "task1";
});
Task<string> task2 = task1.ContinueWith(t =>
{
Console.WriteLine(t.Result);
return "task2";
});
task1.Start();
Console.WriteLine("end");
Console.ReadKey();
}
2.使用函数
private static void fun2()
{
Console.WriteLine("start");
Task<string> task1 = new Task<string>(doTask1);
Task<string> task2 = task1.ContinueWith(doTask2);
task1.Start();
Console.WriteLine("end");
Console.ReadKey();
} private static string doTask1()
{
Console.WriteLine("task0");
return "task1";
} private static string doTask2(Task<string> t)
{
Console.WriteLine(t.Result);
return "task2";
}
运行结果:

6.2 ContineWith和task可能不在同一线程上
例:
private static void fun16()
{
Task.Factory.StartNew(() =>
{
Console.WriteLine($"task {Thread.CurrentThread.ManagedThreadId}");
}).ContinueWith(t =>
{
Console.WriteLine($"ContinueWith {Thread.CurrentThread.ManagedThreadId}");
});
}
运行结果:

七.TaskFactory类
方法:
| StartNew | 创建并启动任务 |
| ContinueWhenAll | 创建一个延续任务,该任务在一组指定的任务完成后开始 |
| ContinueWhenAny | 创建一个延续 System.Threading.Tasks.Task,它将在提供的组中的任何任务完成后马上开始 |
| FromAsync | 创建一个 System.Threading.Tasks.Task`1,表示符合异步编程模型模式的成对的开始和结束方法 |
1.ContinueWhenAll
相当于回调
效果其实和WhenAll差不多,只不过ContineWhenAll采用了回调的方式
使用:带返回值
private static void fun17()
{
Task<string> t1 = Task.Factory.StartNew<string>(() =>
{
Console.WriteLine("task1");
return "task1";
}); Task<string> t2 = Task.Factory.StartNew<string>(() =>
{
Console.WriteLine("task2");
return "task2";
}); Task.Factory.ContinueWhenAll(new Task[] { t1, t2 }, t =>
{
string s = "ContinueWhenAll:";
foreach (Task<string> item in t)
{
s += item.Result;
}
Console.WriteLine(s);
});
}
运行结果:

2.ContinueWhenAny
相当于回调
效果其实和WhenAny差不多,只不过ContineWhenAny采用了回调的方式
使用:
private static void fun18()
{
Task<string> t1 = Task.Factory.StartNew<string>(() =>
{
Thread.Sleep(10);
Console.WriteLine("task1");
return "task1";
}); Task<string> t2 = Task.Factory.StartNew<string>(() =>
{
Console.WriteLine("task2");
return "task2";
}); Task.Factory.ContinueWhenAny(new Task[] { t1, t2 }, t =>
{
Console.WriteLine($"{(t as Task<string>).Result} 先执行完");
});
}
执行结果:

3.FromAsync
相当于异步委托的精简写法,其中ContinueWith相当于异步委托中的callback
使用:
public Task<TResult> FromAsync<TArg1, TResult>(Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod, TArg1 arg1, object state);
static void Main(string[] args)
{
fun20(); Console.WriteLine("Main");
Console.ReadKey();
} private static void fun20()
{
var func = new Func<string, string>(x =>
{
Thread.Sleep(1000);
Console.WriteLine(x);
return "callback";
}); Task.Factory.FromAsync(func.BeginInvoke, func.EndInvoke, "func", null).ContinueWith(t =>
{
Console.WriteLine(t.Result);
});
}
运行结果:

参考:
https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task?view=netframework-4.7.2
https://docs.microsoft.com/zh-cn/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap?view=netframework-4.7.2
https://www.cnblogs.com/leo_wl/archive/2012/03/03/2378695.html#_label2
C#Task学习的更多相关文章
- c# .Net并行和多线程编程之Task学习记录!
任务Task和线程Thread的区别: 1.任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行. 2.任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线 ...
- 微软BI 之SSIS 系列 - Execute SQL Task 中的 Single Row 与 Full Result Set 的处理技巧
开篇介绍 Execute SQL Task 这个控件在微软BI ETL 项目中使用的频率还是非常高的,也是大部分入门 SSIS 初学者最早接触到的几个控制流控件. 我们通常使用 Execute SQL ...
- [.net 多线程]Task
C# 异步编程Task整理(一) c# .Net并行和多线程编程之Task学习记录! .NET 实现并行的几种方式(一) Dispatcher介绍 [C#学习笔记]使用C#中的Dispatcher 用 ...
- 一个demo学会js
全栈工程师开发手册 (作者:栾鹏) 快捷链接: js系列教程1-数组操作全解 js系列教程2-对象和属性全解 js系列教程3-字符串和正则全解 js系列教程4-函数与参数全解 js系列教程5-容器和算 ...
- js系列教程2-对象、构造函数、对象属性全解
全栈工程师开发手册 (作者:栾鹏) 快捷链接: js系列教程1-数组操作全解 js系列教程2-对象和属性全解 js系列教程3-字符串和正则全解 js系列教程4-函数与参数全解 js系列教程5-容器和算 ...
- Siamese Neural Networks for One-shot Image Recognition
one-shot learning简介 这是迁移学习的两种极端形式 zero-shot learning 指的是我们之前没有这个类别的训练样本,但是我们可以学习到一个映射X->Y, 如果这个映射 ...
- Js基础知识2-对象、对象属性全解
Object对象 Object对象包含如下属性和方法,也就意味着一切对象(函数也是对象)都包含如下方法. 每种方法和属性在不同的对象中有不同的作用,并不是每种对象都有使用每个方法的必要. 下面是Obj ...
- Tokio,Rust异步编程实践之路
缘起 在许多编程语言里,我们都非常乐于去研究在这个语言中所使用的异步网络编程的框架,比如说Python的 Gevent.asyncio,Nginx 和 OpenResty,Go 等,今年年初我开始接触 ...
- C#线程和异步
C#Thread学习 C#ThreadPool学习 C#Task学习 C#backgroundWorker c# 锁的使用 C#前台线程和后台线程区别 C#Async,await异步简单介绍 C#委托 ...
随机推荐
- juc线程池原理(五):拒绝策略示例
概要 拒绝策略介绍 线程池的拒绝策略,是指当任务添加到线程池中被拒绝,而采取的处理措施.当任务添加到线程池中之所以被拒绝,可能是由于:第一,线程池异常关闭.第二,任务数量超过线程池的最大限制. 线程池 ...
- MongoDB 4.X搭建
一.MongoDB4.X搭建 1.下载mongdb安装包,在官网上找到对应的版本,我的是centos7 找到上面的连接,通过命令行: 2.将下载的mongodb-linux-x86_64-4.0.0. ...
- php判断是否为ajax请求
先说前端使用 jQuery 时怎么区分: jQuery 发出 ajax 请求时,会在请求头部添加一个名为 X-Requested-With 的信息,信息内容为:XMLHttpRequest 在后端可以 ...
- 23 mysql怎么保证数据不丢失?
MySQL的wal机制,得到的结论是:只要redo log和binlog 持久化到磁盘,就能确保mysql异常重新启动后,数据是可以恢复的. binlog的写入机制 其实,binlog的写入逻辑比较简 ...
- 委托BegionInvoke和窗体BegionInvoke
委托BegionInvoke是指通过委托方法执行多线程任务,例如: //定义委托成员变量 delegate void dg_DeleAirport(); //指定委托函数 dg_DeleAirpor ...
- Python 面向对象的进阶
类的成员 类的成员可以分为三大类 : 字段 , 方法 和 属性 注 : 所有的成员中,只有普通字段的内容保存对象中, 即 : 根据此类创建了对象,在内存就有多少个普通字段. 而其他的成员,则 ...
- leetcode868
class Solution { public: int binaryGap(int N) { ; vector<int> V; while (N) { )//N&1==1,表示最 ...
- 「小程序JAVA实战」 小程序的事件(11)
转自:https://idig8.com/2018/08/09/xiaochengxu-chuji-11/ 我们以前在web开发的时候,web页面也有一些相关的事件,当然小程序要接触屏幕要进行一些点击 ...
- 每天一个Linux命令 - 【groupadd】
[命令]:grouadd [语法]:groupadd [选项] [参数] [功能介绍]:groupadd 命令勇于创建新的工作组,新工作组的信息将被添加的系统文件中. [选项说明]: -g < ...
- Visual C++ Samples-------------Code Project
https://msdn.microsoft.com/en-us/library/hyds2fy1(v=vs.80).aspx