2013-03-26 69 views
-1

我被要求爲我工作的組織編寫一個文檔管理系統,它提供了一系列與不同記錄相關的九個不同工作流程。在這些工作流程中,將文檔添加到「文件」或記錄中,並根據業務規則將這些文檔的子集發佈到公共網站。長時間運行文件操作和執行超時

這些文檔幾乎沒有例外的PDF格式,並且任何一個記錄通常少於20個正在處理。

將其作爲Web應用程序構建的主要原因是將文件保存在我們的數據中心和高速交換機上,而不是通過遠程站點上可能較慢的連接速度在位置之間嘗試複製和備份。

該系統完美地工作,直到一個更大的一系列文件(114個PDF文件完全在大小329MB)超時95%左右的的方式。

的代碼是(IncomingDocuments的類型是List <的FileInfo的> ) -

List<string> filesSuccessfullyAdded = new List<string>(); 

foreach (FileInfo incomingFile in IncomingDocuments) 
{ 
    FileOperations.AddDocument(incomingFile, false, ApplicationCode, (targetDirectoryPath.EndsWith(@"\") ? targetDirectoryPath : targetDirectoryPath + @"\")); 
    FileInfo copiedDocument = new FileInfo(Path.Combine(targetDirectoryPath, incomingFile.Name)); 
    if (copiedDocument.Exists && copiedDocument.Length == incomingFile.Length && copiedDocument.LastWriteTime == incomingFile.LastWriteTime) 
    { 
     filesSuccessfullyAdded.Add(copiedDocument.Name); 
    } 
} 

if (filesSuccessfullyAdded.Any()) 
{ 
    SetupConfirmationLiteral.Text += "<p class='info'>The following files have been successfully added to the application file-</p>"; 

    XDocument successfullyAddedList = new XDocument(
    new XElement("ul", new XAttribute("class", "documentList"))); 

    foreach (string successfulFile in filesSuccessfullyAdded) 
    { 
     successfullyAddedList.Root.Add(new XElement("li", successfulFile)); 
    } 

    SetupConfirmationLiteral.Text += successfullyAddedList.ToString(); 
} 

var notSuccessfullyAdded = from FileInfo incomingDocument in IncomingDocuments 
          where !filesSuccessfullyAdded.Contains(incomingDocument.Name) 
          orderby incomingDocument.Name ascending 
          select incomingDocument.Name; 

if (notSuccessfullyAdded.Any()) 
{ 
    SetupConfirmationLiteral.Text += "<p class='alert'>The following files have <strong>not</strong> been successfully added to the application file-</p>"; 

    XDocument notAddedList = new XDocument(
    new XElement("ul", new XAttribute("class", "documentList"))); 

    foreach (string notAdded in notSuccessfullyAdded) 
    { 
     notAddedList.Root.Add(new XElement("li", notAdded)); 
    } 

    SetupConfirmationLiteral.Text += notAddedList.ToString(); 

    SetupConfirmationLiteral.Text += "<p>A file of the same name may already exist in the target location.</p>"; 
} 

有了一個實用的方法OF-

public static void AddDocument(FileInfo sourceFile, bool appendName, string applicationCode, string targetPath) 
{ 
    try 
    { 
     DirectoryInfo targetDirectory = new DirectoryInfo(targetPath); 
     if (targetDirectory.Exists) 
     { 
      string targetFileName = (appendName ? sourceFile.Name.Insert(sourceFile.Name.IndexOf(sourceFile.Extension, StringComparison.Ordinal), " UPDATED") : sourceFile.Name); 
      if (targetDirectory.GetFiles(targetFileName).Any()) 
      { 
       //Do not throw an exception if the file already exists. Silently return. If the file exists and matches both last modified and size it won't be reported, and can be archived as normal, 
       //otherwise it should be reported to user in the calling method. 
       return; 
      } 
      string targetFileUnc = Path.Combine(targetPath, targetFileName); 
      sourceFile.CopyTo(targetFileUnc, overwrite: false); 
      Logging.FileLogEntry(username: (HttpContext.Current.User.Identity.IsAuthenticated ? HttpContext.Current.User.Identity.Name : "Unknown User"), eventType: LogEventType.AddedDocument, 
       applicationCode: applicationCode, document: sourceFile.Name, uncPath: targetFileUnc); 
     } 
     else 
     { 
      throw new PdmsException("Target directory does not exist"); 
     } 
    } 
    catch (UnauthorizedAccessException ex) 
    { 
     throw new PdmsException("Access was denied to the target directory. Contact the Service Desk.", ex); 
    } 
    catch (PathTooLongException) 
    { 
     throw new PdmsException(string.Format("Cannot add document {0} to the Site File directory for Application {1} - the combined path is too long. Use the Add Documents workflow to re-add documents to this Site File after renaming {0} to a shorter name.", sourceFile.Name, applicationCode)); 
    } 
    catch (FileNotFoundException ex) 
    { 
     throw new PdmsException("The incoming file was not found. It may have already been added to the application file.", ex); 
    } 
    catch (DirectoryNotFoundException ex) 
    { 
     throw new PdmsException("The source or the target directory were not found. The document(s) may have already been added to the application file.", ex); 
    } 
    catch (IOException ex) 
    { 
     throw new PdmsException("Error adding files - file(s) may be locked or there may be server or network problem preventing the copy. Contact the Service Desk.", ex); 
    } 
} 

要進行實際的複製和審計。 PdmsException只是一個特定的異常類,我們用它來向用戶顯示有用的錯誤消息,允許他們在可能的情況下解決自己的問題,或者至少給出可以理解的失敗原因。

我知道我可以簡單地增加ExecutionTimeout屬性(http://msdn.microsoft.com/en-us/library/system.web.configuration.httpruntimesection.executiontimeout.aspx)超過110秒的默認值 - 最多說300秒 - 這可能意味着在這種情況下超時停止發生,但如果用戶試圖添加或發佈數千個文檔。這種解決方案不能很好地擴展,只是推遲而不是解決問題。

我在Visual Studio 2010中使用.NET 4,據我所知,因此必須使用異步的第三方實現,並等待像AsyncBridge(https://nuget.org/packages/AsyncBridge),如果我們想使用ajax來分組文件和更新進度。我無法訪問Visual Studio 2012或甚至比XP更新的Windows,以便使用Microsoft提供的Async定位包。

鑑於這些限制,是否有辦法對這些文檔進行分組/批處理以避免超時,並且(理想情況下)在每批添加時向用戶提供反饋?如果可以直接實施,我願意探索F#。或者,我應該在這個案例上爲Visual Studio 2012辯護嗎?

+0

我很好奇某事..你的組織可以不使用或實現'SharePoint' ..?聽起來像你被要求重新發明'輪子' – MethodMan 2013-03-26 17:28:25

+2

請問那些誰downvoting問題解釋爲什麼?我非常樂意通過一些推理將它帶入下巴。 – pwdst 2013-03-26 17:30:40

+1

由於我們必須與第三方產品集成,因此Sharepoint不是此方案中的選項。 – pwdst 2013-03-26 17:34:09

回答

9

有沒有必要移動到一個完全不同的語言或升級IDE工具,這不是手頭上的問題。目前的問題是,一個基本上爲快速響應而設計的系統(一個Web應用程序)正用於長時間運行的過程。

在Web應用程序中,需要花費不止一點時間的任何事情都應該異步完成。在HTTP的請求/響應模型中,最好(出於多種原因)對提出請求的客戶端做出快速響應。

在長時間運行的過程中,「異步」我並不是指使用AJAX,因爲它仍然是一個請求/響應。

「異步」在這種情況下,我的意思是你想要一個單獨的服務器端進程來處理CPU密集型任務,而Web應用程序只不過是排隊運行的任務並檢查狀態當人們尋找它時的任務。然後它可以在完成任務後報告任務的結果。

所以架構的基本概況會是這樣的:「開始任務」

  • 在web應用程序的用戶點擊一個按鈕,
  • Web應用程序向數據庫表中插入一條記錄,表明該任務已排隊(可能帶有排隊的用戶ID,時間戳,以及其他您需要知道的內容)。
  • 一個單獨的預定應用程序(控制檯應用程序或Windows服務,很可能)永久運行。 (在一個始終運行的Windows服務中使用計時器或計劃反覆運行(例如每隔幾分鐘)作爲控制檯應用程序。)此應用程序將檢查數據庫表以查找新的排隊任務。
  • 當應用程序看到一個任務時,它會在數據庫中將其標記爲「已啓動」(因此後續運行的應用程序不會嘗試並行運行相同的任務)並開始運行它。
  • Web應用程序可以在數據庫表中看到任務的狀態,並將其顯示給請求它的用戶,以便用戶可以看到它仍在運行。
  • 任務完成後,數據庫表中的任務記錄會更新,並將結果存儲在某處。 (取決於結果是什麼數據在數據庫中某種報告文件另存爲某個文件,完全取決於您)
  • Web應用程序可以看到任務的狀態已完成,記錄任何其他信息,並且用戶可以請求查看任務的輸出。

這裏要記住的主要事情是把責任分成兩個應用。 Web應用程序是爲了提供用戶界面。 Web應用程序不適合長時間運行的後臺任務。所以這個責任被轉移到一個更適合這個目的的單獨的應用程序中。兩個應用程序通過共享數據庫進行協調。

因此,正如你在問題結束時暗示的那樣,你可以(也應該)簡單地將任務與應用程序「排隊」,並以用戶認爲合適的方式管理該隊列。