3

我有一个关于多线程应用程序的问题。我使用 TaskFactory 来启动 cpu+time 密集型方法。此方法是对 SAP 的调用,需要很长时间才能完成。用户应该可以选择取消任务。目前我正在使用 thread.Abort(),但我知道这种方法不是取消它的最佳解决方案。有没有人推荐替代品?

代码示例:

Form_LoadAction loadbox = new Form_LoadAction();
Thread threadsapquery = null;

Task.Factory.StartNew<>(() => 
{ 
   t = Thread.CurrentThread;
   Thread.sleep(10000000000); //represents time + cpu intensive method
}

loadbox.ShowDialog();
if (loadbox.DialogResult == DialogResult.Abort)
{
   t.Abort();
}
4

2 回答 2

5

最好的选择是查看该方法是否支持任何类型的合作取消

但是,如果这不可能,那么取消像这样的长时间运行的进程的下一个最佳选择是使用运行长时间运行的进程的第二个可执行文件,然后通过某种形式的 IPC 与该第二个可执行文件通信(命名管道上的 WCF 非常适合内部-machine IPC)“代理”所有呼叫。当您需要取消进程时,您可以终止第二个代理 exe,所有句柄都将被正确释放(在哪里Thread.Abort()不会)。

这是一个完整的例子。有 3 个文件,一个在两个可执行文件之间共享的公共库,其中包含代理的接口和实现,一个托管应用程序和您的客户端应用程序。托管应用程序和公共库可能会合并到一个程序集中。

库数据.dll

//ISapProxy.cs
using System.Collections.Generic;
using System.ServiceModel;

namespace LibraryData
{
    [ServiceContract]
    public interface ISapProxy
    {
        [OperationContract]
        List<SapData> QueryData(string query);

        [OperationContract]
        void Close();
    }
}


//SapProxy.cs
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;

namespace LibraryData
{
    public class SapProxy : ISapProxy
    {
        public List<SapData> QueryData(string query)
        {
            Thread.Sleep(new TimeSpan(0, 0, 5)); //represents time + cpu intensive method

            return new List<SapData>();
        }


        public void Close()
        {
            Application.Exit();
        }
    }
}


//SapData.cs
using System.Runtime.Serialization;

namespace LibraryData
{
    [DataContract]
    public class SapData
    {
    }
}

主机应用程序

//Program.cs
using LibraryData;
using System;
using System.ServiceModel;
using System.Windows.Forms;

namespace HostApp
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            System.Diagnostics.Debugger.Launch();
            if (args.Length > 0)
            {
                var uri = new Uri("net.pipe://localhost");
                using (var host = new ServiceHost(typeof(SapProxy), uri))
                {
                    //If a client connection fails, shutdown.
                    host.Faulted += (obj, arg) => Application.Exit();

                    host.AddServiceEndpoint(typeof(ISapProxy), new NetNamedPipeBinding(), args[0]);
                    host.Open();
                    Console.WriteLine("Service has started and is ready to use.");

                    //Start a message loop in the event the service proxy needs one.
                    Application.Run();

                    host.Close();
                }
            }
        }
    }
}

你的程序.exe

using LibraryData;
using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading.Tasks;

namespace SandboxConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var connectionName = Guid.NewGuid().ToString();
            ProcessStartInfo info = new ProcessStartInfo("HostApp", connectionName);
            info.RedirectStandardOutput = true;
            info.UseShellExecute = false;

            var proxyApp = Process.Start(info);

            //Blocks till "Service has started and is ready to use." is printed.
            proxyApp.StandardOutput.ReadLine();

            var sapProxyFactory = new ChannelFactory<ISapProxy>(new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/" + connectionName));
            Task.Factory.StartNew(() =>
            {
                var sapProxy = sapProxyFactory.CreateChannel();

                try
                {
                    var result = sapProxy.QueryData("Some query");

                    //Do somthing with the result;
                }
                finally
                {
                    sapProxy.Close();
                }
            });

            Console.WriteLine("ready");

            //If you hit enter here before the 5 second pause in the library is done it will kill the hosting process forcefully "canceling" the operation.
            Console.ReadLine();

            proxyApp.Kill();

            Console.ReadLine();

        }
    }
}

我无法完全消除的一个错误是,如果您“快速失败”客户端应用程序(例如通过单击 Visual Studio 中的停止图标),它永远没有机会告诉托管应用程序关闭。

于 2014-01-28T15:03:40.047 回答
-2

你想要使用的是一个取消令牌,它允许你取消你的任务,而不必像现在这样明确地处理线程。

因此,您可以将调用修改为StartNew

var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
var task = Task.Factory.StartNew<>(() => //your logic
     , ts.Token);

然后,如果您需要取消,您只需执行以下操作:

tokenSource2.Cancel();

try
{
    task.Wait();
}
catch(AggregateException aex)
{
    //handle TaskCanceledException here
}

正如评论中提到的那样,除非您处于循环中(鉴于您对 CPU 密集型任务的描述可能不太可能),否则您在 SAP 任务之后将难以清理,但很可能不会比您当前的实现更糟。

这是关于任务取消的一个很好的MSDN 参考。

编辑
感谢 Servy 指出这种方法不适用于 OP 的场景,因为他正在处理一个单一的、长时间运行的方法并且无法检查令牌的状态。我会保留它,但同样,这对 OP 不起作用。

于 2014-01-28T15:05:34.520 回答