如何使动作控制器线程安全?

时间:2014-08-19 20:56:18

标签: c# asp.net-mvc multithreading

在我的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

2 个答案:

答案 0 :(得分:1)

您可以通过在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模式中。锁定文件内存是不够的,因为如果你去过&gt; 1个Web应用程序实例,锁定将按进程而不是每个文件。

答案 1 :(得分:1)

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

更好的方法是写入MemoryStream。