可取消的异步 Web 请求
Cancellable Async Web Request
我正在尝试创建一个通用方法来取消异步 Web 请求以及与之关联的任何其他操作。我发现了另一个与此相关的问题,例如 this.
我已经编写了一个帮助程序 class 来完成这项工作。我在下面展示:
public static class Helpers
{
public static async Task<string> GetJson(string url,
CancellationTokenSource cancellationTokenSource,
bool useSynchronizationContext = true)
{
try
{
var request = (HttpWebRequest)WebRequest.Create(url);
string jsonStringResult;
using (WebResponse response = await request.GetResponseAsync()
.WithCancellation(cancellationTokenSource.Token,
request.Abort, useSynchronizationContext))
{
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
jsonStringResult = await reader.ReadToEndAsync();
reader.Close();
dataStream.Close();
}
cancellationTokenSource.Token.ThrowIfCancellationRequested();
return jsonStringResult;
}
catch (Exception ex) when (ex is OperationCanceledException
|| ex is TaskCanceledException)
{
}
catch (Exception ex) when (ex is WebException
&& ((WebException)ex).Status == WebExceptionStatus.RequestCanceled)
{
}
catch (Exception ex)
{
//Any other exception
}
finally
{
cancellationTokenSource.Dispose();
}
return default;
}
public static async Task<T> WithCancellation<T>(this Task<T> task,
CancellationToken cancellationToken, Action action,
bool useSynchronizationContext)
{
using (cancellationToken.Register(action, useSynchronizationContext))
{
return await task;
}
}
}
注意行
cancellationTokenSource.Token.ThrowIfCancellationRequested();
就在返回 JSON 字符串之前。
当操作被取消并且执行流程在线时
StreamReader reader = new StreamReader(dataStream);
例如,下面的所有行(reader.Close()
等)都将被执行,并且在执行 ThrowIfCancelationRequested()
时将抛出异常 - 是否正确?我错过了什么吗?
如果是这样,有没有办法一次性全部取消?
感谢大家的回复,
在提供了答案和所有真正有用的评论后,我更新了 implementation.I 使用的 HttpClient 和 link 中的扩展方法,使一项实际上不能被取消的任务表现得像一个可以取消的任务- 实际执行了 readAsStringAsync,因为它不能接受取消令牌。
public static class Helpers
{
public static async Task<string> GetJson(string url,CancellationToken cancellationToken)
{
try
{
string jsonStringResult;
using (var client = new HttpClient())
{
cancellationToken.ThrowIfCancellationRequested();
using (var response = await client.GetAsync(url, cancellationToken))
{
jsonStringResult = await response.Content.ReadAsStringAsync().WithCancellation(cancellationToken);
}
}
return jsonStringResult;
}
catch (Exception ex) when (ex is OperationCanceledException)
{
}
catch (Exception ex) when (ex is WebException exception && exception.Status == WebExceptionStatus.RequestCanceled)
{
}
catch (Exception ex)
{
//LogException(ex);
}
return default;
}
public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
return task.IsCompleted
? task: task.ContinueWith(completedTask => completedTask.GetAwaiter().GetResult(),cancellationToken,TaskContinuationOptions.ExecuteSynchronously,TaskScheduler.Default);
}
}
all lines below (reader.Close() etc.) will be executed and the
exception will be thrown when ThrowIfCancelationRequested() is
executed - is that correct? Am I missing something?
If so, is there a way to cancel everything at once?
首先,您要取消的操作必须明确支持被取消。因此,您必须尽力使用代码以某种方式以某种方式将 CancellationToken
作为参数来执行某些操作。如果不可能,那么你就没有其他机会了。
这就是为什么这个概念被称为 合作取消。因为几乎总是双方都应该知道发生了取消。客户端应该知道代码实际上被取消了,客户端仅仅知道取消请求是不够的。对于被调用者,重要的是要了解请求取消以正确完成自身的事实。
关于在执行stream
和reader
的Close
方法时检查操作是否被取消。如果要避免内存泄漏,无论是否取消操作,都必须始终调用清理方法。当然,我建议您使用 using
语句,它会自动进行清理。
顺便说一句,为了使某些功能可取消,您不必在执行每一行之前检查是否请求取消。您只需要在执行一些 long-运行 操作之前和之后检查是否请求取消。如果 long-运行 操作支持通过取消令牌 属性.
取消,则传递取消令牌
此外,您必须查看副作用。如果您已经招致副作用,您的方法不准备在退出时恢复,这会使您处于不一致状态,请不要取消。
一些通用的代码块可能是这样的:
if(ct.IsCancellationRequested)
{
break; // or throw
}
await DoSomething(ct);
if (ct.IsCancellationRequested)
{
// if there is no side-effect
return; // or throw
// or, we already did something in `DoSomething` method
// do some rollback
}
作为解决方案,您可以使用一些不同的对象,如 HttpClient
或 WebRequest
来执行异步、可等待和可取消的 Web 请求。您可以查看 了解实施细节。
我正在尝试创建一个通用方法来取消异步 Web 请求以及与之关联的任何其他操作。我发现了另一个与此相关的问题,例如 this.
我已经编写了一个帮助程序 class 来完成这项工作。我在下面展示:
public static class Helpers
{
public static async Task<string> GetJson(string url,
CancellationTokenSource cancellationTokenSource,
bool useSynchronizationContext = true)
{
try
{
var request = (HttpWebRequest)WebRequest.Create(url);
string jsonStringResult;
using (WebResponse response = await request.GetResponseAsync()
.WithCancellation(cancellationTokenSource.Token,
request.Abort, useSynchronizationContext))
{
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
jsonStringResult = await reader.ReadToEndAsync();
reader.Close();
dataStream.Close();
}
cancellationTokenSource.Token.ThrowIfCancellationRequested();
return jsonStringResult;
}
catch (Exception ex) when (ex is OperationCanceledException
|| ex is TaskCanceledException)
{
}
catch (Exception ex) when (ex is WebException
&& ((WebException)ex).Status == WebExceptionStatus.RequestCanceled)
{
}
catch (Exception ex)
{
//Any other exception
}
finally
{
cancellationTokenSource.Dispose();
}
return default;
}
public static async Task<T> WithCancellation<T>(this Task<T> task,
CancellationToken cancellationToken, Action action,
bool useSynchronizationContext)
{
using (cancellationToken.Register(action, useSynchronizationContext))
{
return await task;
}
}
}
注意行
cancellationTokenSource.Token.ThrowIfCancellationRequested();
就在返回 JSON 字符串之前。
当操作被取消并且执行流程在线时
StreamReader reader = new StreamReader(dataStream);
例如,下面的所有行(reader.Close()
等)都将被执行,并且在执行 ThrowIfCancelationRequested()
时将抛出异常 - 是否正确?我错过了什么吗?
如果是这样,有没有办法一次性全部取消?
感谢大家的回复,
在提供了答案和所有真正有用的评论后,我更新了 implementation.I 使用的 HttpClient 和 link 中的扩展方法,使一项实际上不能被取消的任务表现得像一个可以取消的任务- 实际执行了 readAsStringAsync,因为它不能接受取消令牌。
public static class Helpers
{
public static async Task<string> GetJson(string url,CancellationToken cancellationToken)
{
try
{
string jsonStringResult;
using (var client = new HttpClient())
{
cancellationToken.ThrowIfCancellationRequested();
using (var response = await client.GetAsync(url, cancellationToken))
{
jsonStringResult = await response.Content.ReadAsStringAsync().WithCancellation(cancellationToken);
}
}
return jsonStringResult;
}
catch (Exception ex) when (ex is OperationCanceledException)
{
}
catch (Exception ex) when (ex is WebException exception && exception.Status == WebExceptionStatus.RequestCanceled)
{
}
catch (Exception ex)
{
//LogException(ex);
}
return default;
}
public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
return task.IsCompleted
? task: task.ContinueWith(completedTask => completedTask.GetAwaiter().GetResult(),cancellationToken,TaskContinuationOptions.ExecuteSynchronously,TaskScheduler.Default);
}
}
all lines below (reader.Close() etc.) will be executed and the exception will be thrown when ThrowIfCancelationRequested() is executed - is that correct? Am I missing something?
If so, is there a way to cancel everything at once?
首先,您要取消的操作必须明确支持被取消。因此,您必须尽力使用代码以某种方式以某种方式将 CancellationToken
作为参数来执行某些操作。如果不可能,那么你就没有其他机会了。
这就是为什么这个概念被称为 合作取消。因为几乎总是双方都应该知道发生了取消。客户端应该知道代码实际上被取消了,客户端仅仅知道取消请求是不够的。对于被调用者,重要的是要了解请求取消以正确完成自身的事实。
关于在执行stream
和reader
的Close
方法时检查操作是否被取消。如果要避免内存泄漏,无论是否取消操作,都必须始终调用清理方法。当然,我建议您使用 using
语句,它会自动进行清理。
顺便说一句,为了使某些功能可取消,您不必在执行每一行之前检查是否请求取消。您只需要在执行一些 long-运行 操作之前和之后检查是否请求取消。如果 long-运行 操作支持通过取消令牌 属性.
取消,则传递取消令牌此外,您必须查看副作用。如果您已经招致副作用,您的方法不准备在退出时恢复,这会使您处于不一致状态,请不要取消。
一些通用的代码块可能是这样的:
if(ct.IsCancellationRequested)
{
break; // or throw
}
await DoSomething(ct);
if (ct.IsCancellationRequested)
{
// if there is no side-effect
return; // or throw
// or, we already did something in `DoSomething` method
// do some rollback
}
作为解决方案,您可以使用一些不同的对象,如 HttpClient
或 WebRequest
来执行异步、可等待和可取消的 Web 请求。您可以查看