c#中断异步操作

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c#中断异步操作相关的知识,希望对你有一定的参考价值。

BeginInvoke之后如何手动中断它

异步就是在发生某件事的时候 “自动” 开一个线程去调用注册的委托。所以,你这个问题分2种情况。
一:事件已发生,另一个线程已开启,这种情况,中止异步可以中止当前线程(Thread.CurrentThread.Abort()),也可以直接return退出那个委托啊魂淡!
二:事件未发生,线程还未开启,这种情况下...你想结束什么?直接关掉那个异步对象不就是了!追问

那是在异步内部可以终止,我是说外部,比如异步开始了,然后我在界面上点一个按钮就让它终止

追答

我说的是异步的中止原理,如何从外部干涉到内部,这种事情还要我来说了吗?

追问

再简单的事,也是从不懂到懂的,我就是因为不懂才来问,你知道么就讲一下,大家不都是这样才懂的越来越多么

追答

异步是一种模式,代码运行的模式,它不是一种有状态的对象,所以,所谓的中止异步,其实就是中止这个模式行为(用异步模式执行一个委托),异步的具体行为,视你注册的委托而定。延伸——所谓的中止异步,就是中止那个委托方法的执行。
例:string a = 12345.ToString(); //结果:"12345"
你来给我中止一下12345.ToString()试试?是不是觉得很莫名其妙?因为我们没法控制ToString()!这是一个方法,就只是一句话而已,没错,异步也是这样的。
现在,你只能考虑,有没有什么办法,可以干涉到ToString()方法的内部实现了,比如给string类型加一个cancel属性,只要你设置它为true,就会立刻返回,比如返回:"123" (还没转换完成你就把它中止了)。
幸运的是,异步注册的委托,肯定是你写的,所以你肯定可以在它的内部增加中止机制。比如定义一个全局变量,那个异步委托只要发现变量是true,就立刻return退出当前方法,或者!Thread.CurrentThread.Abort()中止当前线程!
另外,求教要虚心,就因为你不懂,所以我有义务要教你?这是什么神逻辑?你在发现自己不懂的时候,就不会想想,为什么别人懂?难道他们是天生就懂的?没别人努力就不要抱怨。

追问

谢了。关于你另外里说的,我没有认为你教我是一种义务,而有些问题只要知道结果就可以了,毕竟时间有限,知其然和知其所以然之间所需的精力还是差蛮多的。而且现在效率又那么重要,所以只能先知其然,等有空了再知其所以然

参考技术A 精彩答案,不对,异步是无法终止的,必须等待执行完毕!因为你无法确定他的位置! 参考技术B using System;
using System.Net;
using System.Threading;
using System.Text;
using System.IO;

//Request 类用于通过
//异步调用传递数据
public class RequestState

const int BUFFER_SIZE = 1024;
public StringBuilder RequestData;
public byte[] BufferRead;
public HttpWebRequest Request;
public Stream ResponseStream;
//创建适当编码类型的解码器
public Decoder StreamDecode = Encoding.UTF8.GetDecoder();

public RequestState()

BufferRead=new byte[BUFFER_SIZE];
RequestData = new StringBuilder("");
Request = null;
ResponseStream = null;



//ClientGetAsync发出异步请求
class ClientGetAsync

public static ManualResetEvent allDone=new ManualResetEvent(false);
const int BUFFER_SIZE = 1024;
public static void Main(string[] args)

if (args.Length < 1)

showusage();
return;


//从命令行获取URI
Uri HttpSite = new Uri(args[0]);
//创建请求对象
HttpWebRequest wreq = (HttpWebRequest)WebRequest.Create(HttpSite);
//创建状态对象
RequestState rs = new RequestState();
//将请求添加到状态,以便它可以被来回传递
rs.Request = wreq;
//发出异步请求
IAsyncResult r = (IAsyncResult)wreq.BeginGetResponse(new AsyncCallback(RespCallback),rs);

//将ManualResetEvent 设置为Wait
//以便在调用回调前,应用程序不退出
allDone.WaitOne();


public static void showusage()

Console.WriteLine("尝试获取(GET)一个URL");
Console.WriteLine("\r\n用法::");
Console.WriteLine("ClientGetAsync URL");
Console.WriteLine("示例::");
Console.WriteLine("ClientGetAsync http://www.microsoft.con/net/");


private static void RespCallback(IAsyncResult ar)

//从异步结果获取RequestState对象
RequestState rs = (RequestState)ar.AsyncState;
//从RequestState获取HttpWebRequest
HttpWebRequest req = rs.Request;
//调用EndGetResponse生成HttpWebResponse对象
//该对象来自上面发出的请求
HttpWebResponse resp = (HttpWebResponse)req.EndGetResponse(ar);

//既然我们拥有了响应,就该从
//响应流开始读取数据了
Stream ResponseStream = resp.GetResponseStream();

//该读取操作也使用异步完成,所以,
//我们将以RequestState存储流
rs.ResponseStream = ResponseStream;

//rs.BufferRead 被传入到BeginRead.
//这是数据将被读入的位置
IAsyncResult iarRead = ResponseStream.BeginRead(rs.BufferRead,0,BUFFER_SIZE,new AsyncCallback(ReadCallBack),rs);


private static void ReadCallBack(IAsyncResult asyncResult)

//从asyncresult获取RequestState对象
RequestState rs = (RequestState)asyncResult.AsyncState;

//取出在RespCallback中设置的ResponseStream
Stream responseStream = rs.ResponseStream;

//此时,rs.BufferRead中应该有一些数据
//读取操作将告诉我们那里是否有数据
int read = responseStream.EndRead(asyncResult);

if (read > 0)

//准备Char 数组缓冲区,用于向Unicode转换
Char[] charBuffer = new Char[BUFFER_SIZE];

//将字节流转换为Char 数组,然后转换为字符串
//len显示多少字符被转换为Unicode
int len = rs.StreamDecode.GetChars(rs.BufferRead, 0, read, charBuffer, 0);
String str = new String(charBuffer, 0, len);

rs.RequestData.Append(str);

IAsyncResult ar = responseStream.BeginRead(rs.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallBack), rs);

else

if (rs.RequestData.Length > 1)

// 所有数据都已被读取,因此将其显示到控制台
string strContent;
strContent = rs.RequestData.ToString();
Console.WriteLine(strContent);


//关闭响应流
responseStream.Close();

allDone.Set();


return;


2. 判断控件的Handle是在当前线程中,则无需异步执行,否则要异步
if (this.gcDC.InvokeRequired)

OnRegionChangedDelegate dlgt = new OnRegionChangedDelegate(OnLevelRegionChanged);
this.Invoke(dlgt, new object[] sender, ev );

3. 开启新线程
可以通过开启新线程来执行异步操作,并给异步操作方法传递参数。参数通常包括异步方法所需的所有数据。
Thread thread=new Thread(new ParameterizedThreadStart(GetDataFromWebService));
private static void GetDataFromWebService(object entity)
showWindow();
getData();
window.clost();

4.在界面线程外打开程序
resultsCollection.AddRange(entity.DataCol);
InsertIntoTable(resultsCollection, "", entity.tableName, entity.Schema, "", entity.IsNewFetch);
if (window != null)

#region[Close progess window]
System.Windows.Application.Current.Dispatcher.Invoke(new Action(() =>

(window.WpfUIElement as System.Windows.Window).Close();
entity.SetNexValue();
));
#endregion
参考技术C 中断线程。追问

给个事例??

你不一定知晓的C#取消异步操作

(给DotNet加星标,提升.Net技能


cnblogs.com/mi12205599/p/10572840.html


在.NET和C#中运行异步代码相当简单,因为我们有时候需要取消正在进行的异步操作,通过本文可以掌握 通过CancellationToken取消任务(包括non-cancellable任务)。


早期


早期.NET使用 BackgroundWorker 完成异步长时间运行操作。


可以使用CacnelAsync方法设置 CancellationPending = true


private void BackgroundLongRunningTask(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;

for (int i = 1; i <= 10000; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}

// Do something
}
}


已经不再推荐这种方式来完成异步和长时间运行的操作,但是大部分概念在现在依旧可以使用。


Task横空出世


Task代表一个异步操作,该类表示一个异步不返回值的操作, 泛型版本Task<TResult>表示异步有返回值的操作。


可使用async/await 语法糖代码去完成异步操作。

 

以下创建一个简单的长时间运行的操作:


/// <summary>
/// Compute a value for a long time.
/// </summary>
/// <returns>The value computed.</returns>
/// <param name="loop">Number of iterations to do.</param>
private static Task<decimal> LongRunningOperation(int loop)
{
// Start a task and return it
return Task.Run(() =>
{
decimal result = 0;

// Loop for a defined number of iterations
for (int i = 0; i < loop; i++)
{
// Do something that takes times like a Thread.Sleep in .NET Core 2.
Thread.Sleep(10);
result += i;
}

return result;
});
}
// 这里我们使用Thread.Sleep 模仿长时间运行的操作


简单异步调用代码:


public static async Task ExecuteTaskAsync()
{
Console.WriteLine(nameof(ExecuteTaskAsync));
Console.WriteLine("Result {0}", await LongRunningOperation(100));
Console.WriteLine("Press enter to continue");
Console.ReadLine();
}


敲黑板: C#取消异步操作分为



① 让代码可取消(Cancellable)


因为一些原因,长时间运行的操作花费了 冗长的时间(需要取消,避免占用资源);或者不愿意再等待执行结果了我们会取消异步操作。

 

为完成目的需要在 长时间运行的异步任务中传入CancellationToken:


/// <summary>
/// Compute a value for a long time.
/// </summary>
/// <returns>The value computed.</returns>
/// <param name="loop">Number of iterations to do.</param>
/// <param name="cancellationToken">The cancellation token.</param>
private static Task<decimal> LongRunningCancellableOperation(int loop, CancellationToken cancellationToken)
{
Task<decimal> task = null;

// Start a task and return it
task = Task.Run(() =>
{
decimal result = 0;

// Loop for a defined number of iterations
for (int i = 0; i < loop; i++)
{
// Check if a cancellation is requested, if yes,
// throw a TaskCanceledException.

if (cancellationToken.IsCancellationRequested)
throw new TaskCanceledException(task);

// Do something that takes times like a Thread.Sleep in .NET Core 2.
Thread.Sleep(10);
result += i;
}

return result;
});
return task;
}


在长时间运行的操作中监测 IsCancellationRequested方法 (当前是否发生取消命令),这里我倾向去包装一个TaskCanceledException异常类(给上层方法调用者更多处理的可能性); 当然可以调用ThrowIfCancellationRequested方法抛出OperationCanceledException异常。


② 触发取消命令


CancellationToken结构体相当于打入在异步操作内部的楔子,随时等候后方发来的取消命令。


操纵以上CancellationToken状态的对象是 CancellationTokenSource,这个对象是取消操作的命令发布者。

 

默认的构造函数就支持了 超时取消:


// 以下代码 利用 CancellationSource默认构造函数 完成超时取消
public static async Task ExecuteTaskWithTimeoutAsync(TimeSpan timeSpan)
{
Console.WriteLine(nameof(ExecuteTaskWithTimeoutAsync));
using (var cancellationTokenSource = new CancellationTokenSource(timeSpan))
{
try
{
var result = await LongRunningCancellableOperation(500, cancellationTokenSource.Token);
Console.WriteLine("Result {0}", result);
}
catch (TaskCanceledException)
{
Console.WriteLine("Task was cancelled");
}
}
Console.WriteLine("Press enter to continue");
Console.ReadLine();
}


附①: 高阶操作,完成手动取消:


自然我们关注到 CancellationSource 的几个方法, 要想在异步操作的时候 手动取消操作,需要建立另外的线程 等待手动取消操作的指令。


public static async Task ExecuteManuallyCancellableTaskAsync()
{
Console.WriteLine(nameof(ExecuteManuallyCancellableTaskAsync));

using (var cancellationTokenSource = new CancellationTokenSource())
{
// Creating a task to listen to keyboard key press
var keyBoardTask = Task.Run(() =>
{
Console.WriteLine("Press enter to cancel");
Console.ReadKey();

// Cancel the task
cancellationTokenSource.Cancel();
});

try
{
var longRunningTask = LongRunningCancellableOperation(500, cancellationTokenSource.Token);

var result = await longRunningTask;
Console.WriteLine("Result {0}", result);
Console.WriteLine("Press enter to continue");
}
catch (TaskCanceledException)
{
Console.WriteLine("Task was cancelled");
}

await keyBoardTask;
}
}
// 以上是一个控制台程序,异步接收控制台输入,发出取消命令。


附②:高阶操作,取消 non-Cancellable任务 :


有时候,异步操作代码并不提供 对 Cancellation的支持,也就是以上长时间运行的异步操作


LongRunningCancellableOperation(int loop, CancellationToken cancellationToken) 并不提供参数2的传入,相当于不允许 打入楔子。

 

这时我们怎样取消 这样的non-Cancellable 任务?

 

可考虑利用 Task.WhenAny( params tasks) 操作曲线取消:


  • 利用TaskCompletionSource 注册异步可取消任务


  • 等待待non-cancellable 操作和以上建立的 异步取消操作


private static async Task<decimal> LongRunningOperationWithCancellationTokenAsync(int loop, CancellationToken cancellationToken)
{
// We create a TaskCompletionSource of decimal
var taskCompletionSource = new TaskCompletionSource<decimal>();

// Registering a lambda into the cancellationToken
cancellationToken.Register(() =>
{
// We received a cancellation message, cancel the TaskCompletionSource.Task
taskCompletionSource.TrySetCanceled();
});

var task = LongRunningOperation(loop);

// Wait for the first task to finish among the two
var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);

return await completedTask;
}


像上面代码一样执行取消命令 :


public static async Task CancelANonCancellableTaskAsync()
{
Console.WriteLine(nameof(CancelANonCancellableTaskAsync));

using (var cancellationTokenSource = new CancellationTokenSource())
{
// Listening to key press to cancel
var keyBoardTask = Task.Run(() =>
{
Console.WriteLine("Press enter to cancel");
Console.ReadKey();

// Sending the cancellation message
cancellationTokenSource.Cancel();
});
try
{
// Running the long running task
var longRunningTask = LongRunningOperationWithCancellationTokenAsync(100, cancellationTokenSource.Token);
var result = await longRunningTask;

Console.WriteLine("Result {0}", result);
Console.WriteLine("Press enter to continue");
}
catch (TaskCanceledException)
{
Console.WriteLine("Task was cancelled");
}

await keyBoardTask;
}
}


总结


大多数情况下,我们不需要编写自定义可取消任务,因为我们只需要使用现有API。但要知道它是如何在幕后工作总是好的。

 

推荐阅读

(点击标题可跳转阅读)


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

喜欢就点一下「好看」呗~

以上是关于c#中断异步操作的主要内容,如果未能解决你的问题,请参考以下文章

你不一定知晓的C#取消异步操作

C#异步编程简单的运用

同步与异步IO && blocking 与nonblocking 原理

同步与异步IO && blocking 与nonblocking 原理

你不一定知晓的C#取消异步操作

C#进阶系列27 I/O限制的异步操作