【问题标题】:What is the right way of killing a task that works with a file Stream杀死与文件流一起使用的任务的正确方法是什么
【发布时间】:2014-07-01 07:39:03
【问题描述】:

我有一个并行任务,将一些数据写入文件

using (var lFileStream = File.Open(aDestinationFile, shouldResume ? FileMode.Append : FileMode.Create, FileAccess.Write))
{
    try
    {
        while ((lCount = lDownloadStream.Read(lBuf, 0, lBuf.Length)) > 0)
        {
            lFileStream.Write(lBuf, 0, lCount);
        }
    }
    finally
    {
        lFileStream.Close();
    }
}

我通过以下方式启动任务:

Task.Factory.StartNew(() => 
                {
                    try
                    {
                        myMethod();
                    }
                    catch (Exception ex)
                    {
                        Log.Exc(ex);
                    }
                }
            , _resetEvent.Token);

在某些情况下我必须终止任务:

_resetEvent.Cancel(false);
try
{
    _task.Dispose();
}
catch
{
}

任务停止后,我开始了一个新任务,但是当我尝试访问已使用的文件时,我得到了:

进程无法访问文件'bla-bla',因为它正在被使用 由另一个进程。

我怎样才能正确地“杀死”任务?

【问题讨论】:

  • 您不需要您的 try-finaly 阻止从您的 using 块调用的 Dispose() 为您调用 Close()
  • @ScottChamberlain 更正您所说的:using 不会调用 Close,它将调用 Dispose,而后者又会调用 Close
  • @SriramSakthivel 这就是我想说的(谢谢你让它不那么复杂)

标签: c# file-io task cancellation


【解决方案1】:

当您将CancellationToken 传递给任务时,如果令牌在任务调度程序开始执行任务时被取消,那么所做的只是阻止任务启动。一旦任务已经启动,它对任务没有影响。

您需要做的是“合作取消”,您需要检查在任务内部运行的代码中的令牌,并让它取消正在执行的操作。最简单的方法是将 CancelationToken 传递给方法本身,然后运行函数 ThrowIfCancellationRequested();,您的程序将抛出 OperationCanceledException 并执行您设置的任何清理以处理该异常。

private void myMethod(CancellationToken token)
{
    using (var lFileStream = File.Open(aDestinationFile, shouldResume ? FileMode.Append : FileMode.Create, FileAccess.Write))
    {
        //The try-finally was not nessessary, Dispose() will call Close() for you.
        while ((lCount = lDownloadStream.Read(lBuf, 0, lBuf.Length)) > 0)
        {
            token.ThrowIfCancellationRequested();
            lFileStream.Write(lBuf, 0, lCount);
        }
    }
}

Task.Factory.StartNew(() => 
                {
                    try
                    {
                        myMethod(_resetEvent.Token);
                    }
                    catch (Exception ex)
                    {
                        //If the task was canceled we don't need to log the exception.
                        if(!ex is OperationCanceledException)
                            Log.Exc(ex);
                    }
                }
            , _resetEvent.Token);

为了减少重构,您可以对您的 writer 方法执行以下操作

//All your old code can still call this method.
public void myMethod()
{
    myMethod(CancellationToken.None); //Call the overload with a empty token.
}

//New code that needs to cancel the operation can call this method.
public void myMethod(CancellationToken token)
{
     //Slightly modified old Writer code that uses the CancelationToken inside any loops or in between any long running operations that can't be interrupted.
}

【讨论】:

  • 我做对了吗?“合作取消”是唯一的方法吗?我在问这个问题,因为“编写器”方法是我的解决方案中其他方法也使用的代码。这就是为什么我想避免重构
  • 是的,写入方法必须与取消“合作”,否则您必须等待写入完成才能被系统“关闭”。看我的更新,让你重构更轻松,你只需要修改 writer 函数和任何你想支持取消操作的函数,writer 函数的所有其他用途都可以保持不变。
  • 你为什么使用Task.Factory.StartNew。您想将Stream.BeginWriteStream.EndWriteTaskFactory.FromAsync 一起使用。
  • 如果您在 .NET 4.0 中使用 .NET 4.5 或 Microsoft.Bcl.Async,为了使其响应更快,您可以让您的代码使用 async/await 并将 void Stream.Write(byte[], int, int) 调用替换为 Task Stream.WriteAsync(byte[], int, int, CancelationToken) 和将令牌传递给 write 函数。这将使您的函数在令牌被提出后立即停止,而不是等待Write( 返回。
  • @ScottChamberlain Stream.WriteAsync 是 4.5 而不是 4.0
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-10
  • 2013-01-02
  • 1970-01-01
  • 2020-08-27
  • 2020-07-10
  • 2020-09-29
相关资源
最近更新 更多