Question

i have a question about multithreading applications. I use the TaskFactory to start a cpu+time intensive method. This method is a call to SAP and needs a long time to finish. The user should have an option to cancel the task. Currently i am using thread.Abort(), but i know that this method isn't the best solution to cancel it. Does anyone have a recommendation for an alternative?

Code Example:

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();
}
Était-ce utile?

La solution

The best option is to see if the method supports any kind of cooperative cancelation.

However, if that is not possible the next best option to canceling a long running process like that is use a 2nd executable that runs the long running process then communicate with that 2nd executable over some form of IPC (WCF over Named Pipes works great for intra-machine IPC) to "proxy" all of the calls. When you need to cancel your process you can kill the 2nd proxy exe and all handles will properly be released (where Thread.Abort() would not).

Here is a complete example. There are 3 files, a common library that is shared between both executables that holds the interfaces and implementations of the proxy, a hosting app and your client app. The hosting app and common library could potentially be combined in to a single assembly.

LibraryData.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
    {
    }
}

HostApp.exe

//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();
                }
            }
        }
    }
}

YourProgram.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();

        }
    }
}

One bug I could not squash completely is if you "Fail Fast" the client app (like by clicking the stop icon in visual studio) it never has the opportunity to tell the hosting app to shutdown.

Autres conseils

What you want to use is a Cancellation Token which will allow you to cancel your task without having to muck with threads explicitly like you currently are.

So, you'd modify your call to StartNew like this:

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

Then, if you need to cancel, you just do this:

tokenSource2.Cancel();

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

As alluded to in the comments, unless you are in a loop (which given your description of a CPU intensive task is probably unlikely), you will have trouble cleaning up after your SAP task, but most likely no worse than your current implementation.

Here's a good MSDN reference on task cancellation.

EDIT
Thanks to Servy for pointing out that this approach won't work for OP's scenario since he is dealing with a single, long-running method and can't check the state of the token. I'll leave it up, but again, this won't work for the OP.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top