第一篇的内容请看这里: http://www.cnblogs.com/leizhang/archive/2012/04/13/2446220.html
代码下载在文章最后
如何取消正在进行的异步操作
我们从前文可以知道我们进行的异步操作主要如下:
- 下载一个网页
- 通过正则表达式分析网页
我们可以发现步骤1是唯一耗时的操作,而且如果在我们点击取消按钮的时候我们如果已经下载了某一个网页我们也没必要中止第二步操作。
因此我们希望点击按钮的时候能够中止所有正在下载网页的异步操作.
这是我们之前的下载函数:
遗憾的是DownloadDataTaskAsync没有提供支持异步取消的重载,因此我们需要自己来自定义一个,我们先查看DownloadDataTaskAsync本身的代码:
// System.Net.WebClient
[ComVisible(false)]
[HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
public Task<byte[]> DownloadDataTaskAsync(Uri address)
{
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>(address);
DownloadDataCompletedEventHandler handler = null;
handler = delegate(object sender, DownloadDataCompletedEventArgs e)
{
this.HandleCompletion<DownloadDataCompletedEventArgs, DownloadDataCompletedEventHandler, byte[]>(tcs, e, (DownloadDataCompletedEventArgs args) => args.Result, handler, delegate(WebClient webClient, DownloadDataCompletedEventHandler completion)
{
webClient.DownloadDataCompleted -= completion;
});
};
this.DownloadDataCompleted += handler;
try
{
this.DownloadDataAsync(address, tcs);
}
catch
{
this.DownloadDataCompleted -= handler;
throw;
}
return tcs.Task;
}
我们将使用扩展函数的方法为DownloadDataTaskAsync添加一个可以异步取消的重载:
public static Task<byte[]> DownloadDataTaskAsync(this WebClient webClient, Uri address, CancellationToken cancellationToken)
如果我们先不考虑扩展函数必须是静态的话,我们对原函数进行改造:
// System.Net.WebClient
[ComVisible(false)]
[HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
public Task<byte[]> DownloadDataTaskAsync(Uri address, CancellationToken cancellationToken)
{
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>(address);
if (cancellationToken.IsCancellationRequested)
{
tcs.TrySetCanceled();
}
DownloadDataCompletedEventHandler handler = null;
handler = delegate(object sender, DownloadDataCompletedEventArgs e)
{
this.HandleCompletion<DownloadDataCompletedEventArgs, DownloadDataCompletedEventHandler, byte[]>(tcs, e, (DownloadDataCompletedEventArgs args) => args.Result, handler, delegate(WebClient webClient, DownloadDataCompletedEventHandler completion)
{
webClient.DownloadDataCompleted -= completion;
});
};
this.DownloadDataCompleted += handler;
try
{
this.DownloadDataAsync(address, tcs);
if (cancellationToken.IsCancellationRequested)
{
this.CancelAsync();
}
}
catch
{
this.DownloadDataCompleted -= handler;
throw;
}
return tcs.Task;
}
我们注意因为扩展函数必须是静态的所以this.HandleCompletion显然无法使用了,构造一个类解决这个问题:
internal static class EAPCommon
{
internal static void HandleCompletion<T>(TaskCompletionSource<T> tcs, bool requireMatch, AsyncCompletedEventArgs e, Func<T> getResult, Action unregisterHandler)
{
if (requireMatch)
{
if (e.UserState != tcs)
{
return;
}
}
try
{
unregisterHandler();
}
finally
{
if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else
{
if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else
{
tcs.TrySetResult(getResult());
}
}
}
}
}
现在我们可以来构造我们的扩展函数:
internal static class AsyncExtensions
{
public static Task<byte[]> DownloadDataTaskAsync(this WebClient webClient, Uri address, CancellationToken cancellationToken)
{
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>(address);
if (cancellationToken.IsCancellationRequested)
{
tcs.TrySetCanceled();
}
else
{
CancellationTokenRegistration ctr = cancellationToken.Register(new Action(webClient.CancelAsync));
DownloadDataCompletedEventHandler completedHandler = null;
completedHandler = delegate(object sender, DownloadDataCompletedEventArgs e)
{
EAPCommon.HandleCompletion<byte[]>(tcs, true, e, () => e.Result, delegate
{
ctr.Dispose();
webClient.DownloadDataCompleted -= completedHandler;
});
};
webClient.DownloadDataCompleted += completedHandler;
try
{
webClient.DownloadDataAsync(address, tcs);
if (cancellationToken.IsCancellationRequested)
{
webClient.CancelAsync();
}
}
catch
{
webClient.DownloadDataCompleted -= completedHandler;
throw;
}
}
return tcs.Task;
}
}
好的现在我们有了可以异步取消的异步下载函数(真拗口),我们来改写原来的程序:
新建如图变量:
在获取按钮单击事件中初始化它:
添加取消按钮单击事件,并添加如下代码:
重写下载函数:
修改用到下载函数的所有地方:
修改完毕。
注意我们新的下载函数会在调试时抛出异常,建议不要运行调试而是直接到文件夹运行程序:
点击取消按钮程序程序立即成功中止。
关于导出数据
数据导出非常简单且方式多种多样,所以这里就不浪费大家时间了,建议大家导出为csv文件这样可以直接用Excel打开,而且csv是文本格式的逗号分割文件,不会有额外开销,省时省力划算多了。
下面给出代码下载,不包括数据导出: