MSDN.WhiteKnight - Stack Overflow answers
Ответ на "Принудительная отмена задачи"
Answer 655882
Нашел один способ принудительного завершения задачи без нарушения работы приложения. Данный способ позволяет завершить задачу, запущенную с параметром TaskCreationOptions.LongRunning, зная ID рабочего потока. Основан на вызове функции ExitThread в контексте целевого потока с помощью недокументированной функции RtlRemoteCall. Способ не работает, если поток бесконечно находится в состоянии ожидания, можно завершить только работающий поток. Т.е это не на 100% надежно, но, я полагаю, получше чем Thread.Abort.
Если задача исполняет ваш собственный код, ID рабочего потока легко получить, вызывая из нее функцию GetCurrentThreadId и сохраняя результат в переменной. Если задача исполняет чужой код (например, подгружаемый из внешней DLL), можно его узнать только выборкой из потоков по времени старта задачи.
Основной класс:
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Diagnostics; using System.Runtime.InteropServices; namespace TaskTest { public class TaskKiller { const int THREAD_ACCESS_TERMINATE = (0x0001); const int SYNCHRONIZE = (0x00100000); const int STANDARD_RIGHTS_REQUIRED = (0x000F0000); const int THREAD_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF); [DllImport("kernel32.dll")] public static extern IntPtr OpenThread(uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr hObject); [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId(); [DllImport("kernel32.dll")] public static extern IntPtr GetCurrentProcess(); [DllImport("kernel32.dll")] public static extern IntPtr GetCurrentThread(); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("ntdll.dll", ExactSpelling = true, EntryPoint = "RtlRemoteCall")] static extern int RtlRemoteCall( IntPtr Process, IntPtr Thread, IntPtr CallSite, uint ArgumentCount, IntPtr Arguments, uint PassContext, uint AlreadySuspended ); /// <summary> /// Завершение потока с указанным ID /// </summary> /// <returns>0 при успешном завершении, код NTSTATUS при ошибке</returns> public static int KillThreadById(uint threadid) { IntPtr hModule = (IntPtr)0; IntPtr hThread = (IntPtr)0; IntPtr unmanagedPointer = (IntPtr)0; try { /*Получение адреса функции ExitThread*/ hModule = GetModuleHandle(@"kernel32.dll"); IntPtr pProc = (IntPtr)0; pProc = GetProcAddress(hModule, "ExitThread"); /*Получение дескриптора потока с полным доступом*/ hThread = TaskKiller.OpenThread( (uint)(THREAD_ALL_ACCESS), false, (uint)threadid); IntPtr hProcess = GetCurrentProcess(); int[] args = new int[] { (int)0 };//массив аргументов для RtlRemoteCall unmanagedPointer = Marshal.AllocHGlobal(args.Length * sizeof(int));//выделение блока неуправлемой памяти Marshal.Copy(args, 0, unmanagedPointer, args.Length);//копирование массива в неуправляемую память /*Вызов ExitThread в контексте завершаемого потока*/ int result = RtlRemoteCall(hProcess, hThread, pProc, 1, unmanagedPointer, 0, 0); return result; } finally { // Clean up resources if(unmanagedPointer !=(IntPtr)0)Marshal.FreeHGlobal(unmanagedPointer); if (hThread != (IntPtr)0) TaskKiller.CloseHandle(hThread); if (hModule != (IntPtr)0) TaskKiller.CloseHandle(hModule); } } /// <summary> /// Получение ID всех потоков, стартовавших в указанном интервале времени /// </summary> public static List<uint> GetThreadsByStartTime(DateTime t1, DateTime t2) { List<uint> threads = new List<uint>(); Process pr=Process.GetCurrentProcess(); using (pr) { ProcessThreadCollection ths = pr.Threads; foreach (ProcessThread th in ths) { using (th) { if (th.TotalProcessorTime.TotalMilliseconds > 0) { if (DateTime.Compare(th.StartTime, t1) >= 0 && DateTime.Compare(th.StartTime, t2) <= 0) threads.Add((uint)th.Id); } } } } return threads; } } }
Пример использования:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; using System.Threading.Tasks; using System.Diagnostics; using System.Runtime.InteropServices; namespace TaskTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); PrintThreads(); } int i = 0; uint threadid;//ID рабочего потока DateTime t;//время старта задачи Task t1=null;//задача void PrintThreads() { Process pr=Process.GetCurrentProcess(); using (pr) { ProcessThreadCollection ths = pr.Threads; StringBuilder b = new StringBuilder(300); b.AppendLine("Threads: " + ths.Count); foreach (ProcessThread th in ths) { using (th) { b.AppendLine(th.Id + " - " + th.ThreadState.ToString()+" - "+th.StartTime); } } textBox1.Text += b.ToString(); } } private void button1_Click(object sender, EventArgs e) { /*Делегат для задачи*/ Action action = () => { threadid = TaskKiller.GetCurrentThreadId(); //сохранить ID потока для последующего доступа while (true)// Just loop. { i++; } }; // Construct an unstarted task t1 = new Task(action,TaskCreationOptions.LongRunning); // Launch task t1.Start(); t = DateTime.Now;//сохранить время старта для последующего использования textBox1.Text = "Task started"+Environment.NewLine; PrintThreads(); } private void button2_Click(object sender, EventArgs e) { //Завершение потока, если известен его ID textBox1.Text = "-- Before terminating --" + Environment.NewLine; PrintThreads(); textBox1.Text += Environment.NewLine; int res=TaskKiller.KillThreadById(threadid); if (res != 0) { textBox1.Text += ("Error NTSTATUS=" + res.ToString("X")); } else { textBox1.Text += threadid.ToString() + " is terminated!"; } textBox1.Text += Environment.NewLine; textBox1.Text += "-- After terminating --" + Environment.NewLine; PrintThreads(); } private void bTerminate_Click(object sender, EventArgs e) { //Завершение потоков по времени старта textBox1.Text = "-- Before terminating --" + Environment.NewLine; PrintThreads(); textBox1.Text += "-----------------------"; textBox1.Text += Environment.NewLine; List<uint> threads=TaskKiller.GetThreadsByStartTime( t.Subtract(TimeSpan.FromSeconds(1)), t.Add(TimeSpan.FromSeconds(1)) ); foreach (uint id in threads) { TaskKiller.KillThreadById(id); textBox1.Text += id.ToString() + " is terminated!"; textBox1.Text += Environment.NewLine; } textBox1.Text += "-- After terminating --" + Environment.NewLine; PrintThreads(); textBox1.Text += "-----------------------"; } } }
Content is retrieved from StackExchange API.
Auto-generated by ruso-archive tools.