【问题标题】:How to make action controller thread safe?如何使动作控制器线程安全?
【发布时间】:2014-08-19 20:56:17
【问题描述】:

在我的 MVC 3 应用程序中,我需要每 5 分钟调用一次动作控制器。 在这个动作控制器中,我将读取并解析一个 XML 文件。 所以当我有很多客户端同时调用 action 方法时,我会得到 IO 异常。我怎样才能避免这个问题并使我的动作调用线程安全。 我的控制器操作如下所示:

public PartialViewResult _warningsView(string containerId)
{
    Debug.WriteLine("_warningsView");
    var myXslTrans = new XslCompiledTransform();
    myXslTrans.Load(@"E:/web/data/xml/alert.xslt");
    myXslTrans.Transform(@"E:/.../alert.xml", @"E:/.../TransAlert.xml");
    XmlSerializer serializer = new XmlSerializer(typeof(warnings));
    using (FileStream fileStream = new FileStream(@"E:/.../TransAlert.xml", FileMode.Open))
    {
        warnings result = (warnings)serializer.Deserialize(fileStream);
        return new Ext.Net.MVC.PartialViewResult
        {
            RenderMode = RenderMode.AddTo,
            ContainerId = containerId,
            Model = result.warningList,
            WrapByScriptTag = false
        };
    }
}

我忘了提到错误:

[IOException: The process cannot access the file 'E:\web\data\xml\TransAlert.xml' because it is being used by another process.]
   System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) +10527069
   System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost) +1305
   System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy) +60
   System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean useAsync) +54
   System.Xml.XmlWriterSettings.CreateWriter(String outputFileName) +155
   System.Xml.XmlWriter.Create(String outputFileName, XmlWriterSettings settings) +23
   System.Xml.Xsl.XslCompiledTransform.Transform(String inputUri, String resultsFile) +91
   WIS_3_0.Controllers.HomeController._warningsView(String containerId) in C:\Users\Mohamed\Documents\Visual Studio 2010\Projects\wis30\WIS_3_0\Controllers\HomeController.cs:92
   lambda_method(Closure , ControllerBase , Object[] ) +180
   System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +14
   System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +214
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +27
   System.Web.Mvc.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12() +55
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +253
   System.Web.Mvc.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() +21
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +253
   System.Web.Mvc.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() +21
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +191
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +324
   System.Web.Mvc.Controller.ExecuteCore() +106
   System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +91
   System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +10
   System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +34
   System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +19
   System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +10
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
   System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +48
   System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f) +7
   System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action) +22
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +60
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9514928
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

【问题讨论】:

  • 您得到的确切异常是什么?请发布ex.ToString()的结果。
  • 您究竟从哪里得到 IO 异常?它说了什么?
  • 这个文件会随着时间而改变吗?
  • 你为什么要转换成文件?使用适用于流的重载之一并使用MemoryStream
  • @JohnSaunders 异常已添加到帖子中。谢谢

标签: c# asp.net-mvc multithreading


【解决方案1】:

您有多个请求写入同一个文件!如果您坚持使用文件进行输出,则应写入临时文件(每个请求的文件名唯一)。

写入 MemoryStream 会更好。

【讨论】:

  • 多个请求如何共享临时文件名或 MemoryStream?我们使用的是 mvc 控制器,所以每个客户端请求都不会记住其他请求
  • 我没有说它会共享临时文件名。您没有使用临时文件。您为每个请求使用相同的文件。试试Path.GetTempFileName 方法。
  • 在这种情况下,如何知道最后一个相关的临时文件是什么?我什么时候删除临时文件?
  • 你不需要提前知道名字是什么。获取名称,对文件执行您想要执行的操作,完成后将其删除。
【解决方案2】:

您可以通过在the constructor 中指定FileShare 参数来允许多个线程同时读取文件,无一例外:

using (FileStream fileStream = new FileStream(@"E:/.../TransAlert.xml", 
        FileMode.Open, FileAccess.Read, FileShare.Read))
{
    // Your code here
}

根据 Quintium 的评论,这是代码当前构造函数调用的默认行为。因此,异常可能与您程序的另一个方面有关。当您提供异常详细信息时,我本人和/或其他人将能够为您提供进一步的帮助。

因异常详情而编辑

您提供的错误[IOException: The process cannot access the file 'E:\web\data\xml\TransAlert.xml' because it is being used by another process.] 表示该文件已被另一个线程或进程锁定。在这种情况下,堆栈跟踪说明了故事:

System.Xml.Xsl.XslCompiledTransform.Transform(String inputUri, String resultsFile) +91

这表明这条线很可能是你的问题:

myXslTrans.Transform(@"E:/.../alert.xml", @"E:/.../TransAlert.xml");

我相信Transform 会导致 WRITE 操作(尽管我从未使用过它)。这将被导致此异常的其他 READING 操作阻止。

要以一种快速且相当简单的方式解决此问题,您需要使用经典的文件访问方法:自旋锁。

自旋锁定:

最简单的方法是将您的写入尝试包装在一个 try-catch 块中,如果捕获到异常,则休眠 X 时间,然后重试写入。这是检查文件系统访问的唯一“原子”方式。

另一种常见的方法是在写入时将标记文件(例如“工作”)写入文件夹。然后,在让读者做任何工作之前检查它(并自旋锁并再次检查,直到它被删除)。写入完成后,写入器会删除标记文件,让读取器再次访问。

请注意,由于检查文件访问和实际访问文件之间的竞争条件,写入者和读取者都需要包装在 try-catch-spin 模式中。在文件上锁定内存是不够的,因为如果您访问 > 1 个 Web 应用程序实例,锁定将针对每个进程而不是每个文件。

【讨论】:

  • @Quintium 你是对的。那多奇怪。我们需要异常详细信息!
  • @Haney 帖子已更新异常详情
猜你喜欢
  • 2017-06-24
  • 2012-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-20
  • 2021-10-13
相关资源
最近更新 更多