停止挂同步方法

Stop hanging synchronous method

XenAPI 中有一个方法HTTP_actions.put_import(),它是同步的,它支持通过其委托取消

我有以下方法:

private void UploadImage(.., Func<bool> isTaskCancelled)
{
    try
    {
        HTTP_actions.put_import(
            cancellingDelegate: () => isTaskCancelled(),
            ...);
    }
    catch (HTTP.CancelledException exception)
    {
    }
}

碰巧在某些情况下此方法 HTTP_actions.put_import 挂起并且对 isTaskCancelled() 没有反应。在那种情况下,整个应用程序也会挂起。

我可以运行这个方法在一个单独的线程中,一旦我收到取消信号就强行杀死它,但是这个方法并不总是挂起,有时我想优雅地取消这个方法。只有当这个方法真的挂了,我才想自己kill掉

处理这种情况的最佳方法是什么?

HTTP_actions.put_import 打电话 HTTP_actions.put 打电话 HTTP.put 打电话 HTTP.CopyStream

委托被传递给 CopyStream,然后检查函数是否为 null(未传递)或 true(return 值)。但是,它只在 While 语句中执行此操作,因此很可能是 StreamRead 导致了阻塞操作。尽管它也可能出现在 progressDelegate 中,如果使用的话。

为了解决这个问题,将对 HTTP.put_import() 的调用放在任务或后台线程中,然后分别检查取消或 task/thread 中的 return。

有趣的是,快速浏览一下 CopyStream 代码,我发现了一个错误。如果在进程被取消时计算出的函数 return 是基于它正在进行的一些检查的不同值,那么实际上您可以让循环退出而不生成 CancelledException()CancelledException 调用的结果应存储在局部变量中。

为以下内容撰写博客 post:http://pranayamr.blogspot.in/2017/12/abortcancel-task.html

自上次 2 小时以来为您尝试了很多解决方案,我想出了以下可行的解决方案,请尝试一下

class Program
{
   //capture request running that , which need to be cancel in case
   // it take more time 
    static Thread threadToCancel = null;
    static async Task<string> DoWork(CancellationToken token)
    {
        var tcs = new TaskCompletionSource<string>();
        //enable this for your use
    //await Task.Factory.StartNew(() =>
    //{
    //    //Capture the thread
    //    threadToCancel = Thread.CurrentThread;
    //    HTTP_actions.put_import(...); 
    //});
    //tcs.SetResult("Completed");
    //return tcs.Task.Result;

    //comment this whole this is just used for testing 
        await Task.Factory.StartNew(() =>
        {
            //Capture the thread
            threadToCancel = Thread.CurrentThread;

            //Simulate work (usually from 3rd party code)
            for (int i = 0; i < 100000; i++)
            {
                Console.WriteLine($"value {i}");
            }

            Console.WriteLine("Task finished!");
        });

        tcs.SetResult("Completed");
        return tcs.Task.Result;
    }


    public static void Main()
    {
        var source = new CancellationTokenSource();
        CancellationToken token = source.Token;
        DoWork(token);
        Task.Factory.StartNew(()=>
        {
            while(true)
            {
                if (token.IsCancellationRequested && threadToCancel!=null)
                {
                    threadToCancel.Abort();
                    Console.WriteLine("Thread aborted");
                }
            }
        });
        ///here 1000 can be replace by miliseconds after which you want to 
        // abort thread which calling your long running method 
        source.CancelAfter(1000);
        Console.ReadLine();   
    }
}

这是我的最终实现(基于 Pranay Rana 的回答)。

public class XenImageUploader : IDisposable
{
    public static XenImageUploader Create(Session session, IComponentLogger parentComponentLogger)
    {
        var logger = new ComponentLogger(parentComponentLogger, typeof(XenImageUploader));

        var taskHandler = new XenTaskHandler(
            taskReference: session.RegisterNewTask(UploadTaskName, logger),
            currentSession: session);

        return new XenImageUploader(session, taskHandler, logger);
    }

    private XenImageUploader(Session session, XenTaskHandler xenTaskHandler, IComponentLogger logger)
    {
        _session = session;
        _xenTaskHandler = xenTaskHandler;
        _logger = logger;

        _imageUploadingHasFinishedEvent = new AutoResetEvent(initialState: false);

        _xenApiUploadCancellationReactionTime = new TimeSpan();
    }

    public Maybe<string> Upload(
        string imageFilePath,
        XenStorage destinationStorage,
        ProgressToken progressToken,
        JobCancellationToken cancellationToken)
    {
        _logger.WriteDebug("Image uploading has started.");

        var imageUploadingThread = new Thread(() =>
            UploadImageOfVirtualMachine(
                imageFilePath: imageFilePath,
                storageReference: destinationStorage.GetReference(),
                isTaskCancelled: () => cancellationToken.IsCancellationRequested));

        imageUploadingThread.Start();

        using (new Timer(
            callback: _ => WatchForImageUploadingState(imageUploadingThread, progressToken, cancellationToken),
            state: null,
            dueTime: TimeSpan.Zero,
            period: TaskStatusUpdateTime))
        {
            _imageUploadingHasFinishedEvent.WaitOne(MaxTimeToUploadSvm);
        }

        cancellationToken.PerformCancellationIfRequested();

        return _xenTaskHandler.TaskIsSucceded
            ? new Maybe<string>(((string) _xenTaskHandler.Result).GetOpaqueReferenceFromResult())
            : new Maybe<string>();
    }

    public void Dispose()
    {
        _imageUploadingHasFinishedEvent.Dispose();
    }

    private void UploadImageOfVirtualMachine(string imageFilePath, XenRef<SR> storageReference, Func<bool> isTaskCancelled)
    {
        try
        {
            _logger.WriteDebug("Uploading thread has started.");

            HTTP_actions.put_import(
                progressDelegate: progress => { },
                cancellingDelegate: () => isTaskCancelled(),
                timeout_ms: -1,
                hostname: new Uri(_session.Url).Host,
                proxy: null,
                path: imageFilePath,
                task_id: _xenTaskHandler.TaskReference,
                session_id: _session.uuid,
                restore: false,
                force: false,
                sr_id: storageReference);

            _xenTaskHandler.WaitCompletion();

            _logger.WriteDebug("Uploading thread has finished.");
        }
        catch (HTTP.CancelledException exception)
        {
            _logger.WriteInfo("Image uploading has been cancelled.");
            _logger.WriteInfo(exception.ToDetailedString());
        }

        _imageUploadingHasFinishedEvent.Set();
    }

    private void WatchForImageUploadingState(Thread imageUploadingThread, ProgressToken progressToken, JobCancellationToken cancellationToken)
    {
        progressToken.Progress = _xenTaskHandler.Progress;

        if (!cancellationToken.IsCancellationRequested)
        {
            return;
        }

        _xenApiUploadCancellationReactionTime += TaskStatusUpdateTime;

        if (_xenApiUploadCancellationReactionTime >= TimeForXenApiToReactOnCancel)
        {
            _logger.WriteWarning($"XenApi didn't cancel for {_xenApiUploadCancellationReactionTime}.");

            if (imageUploadingThread.IsAlive)
            {
                try
                {
                    _logger.WriteWarning("Trying to forcefully abort uploading thread.");

                    imageUploadingThread.Abort();
                }
                catch (Exception exception)
                {
                    _logger.WriteError(exception.ToDetailedString());
                }
            }

            _imageUploadingHasFinishedEvent.Set();
        }
    }

    private const string UploadTaskName = "Xen image uploading";

    private static readonly TimeSpan TaskStatusUpdateTime = TimeSpan.FromSeconds(1);
    private static readonly TimeSpan TimeForXenApiToReactOnCancel = TimeSpan.FromSeconds(10);
    private static readonly TimeSpan MaxTimeToUploadSvm = TimeSpan.FromMinutes(20);

    private readonly Session _session;
    private readonly XenTaskHandler _xenTaskHandler;
    private readonly IComponentLogger _logger;

    private readonly AutoResetEvent _imageUploadingHasFinishedEvent;

    private TimeSpan _xenApiUploadCancellationReactionTime;
}