簡體   English   中英

多個用戶在同一個文件上寫入

[英]Multiple users writing at the same file

我有一個項目是一個Web API項目,我的項目由多個用戶訪問(我的意思是真的很多用戶)。 當我的項目從前端(使用HTML 5的網頁)訪問,並且用戶執行更新或檢索數據之類的操作時,后端應用程序(Web API)將編寫單個日志文件(.log文件,但內容為JSON)。 問題是,當被多個用戶訪問時,前端變得沒有響應(總是加載)。 問題在於編寫日志文件的過程(真正非常多的用戶正在訪問單個日志文件)。 我聽說使用多線程技術可以解決問題,但我不知道哪種方法。 所以,也許任何人都可以幫助我。 這是我的代碼(對不起,如果錯字,我使用我的智能手機和移動版本的堆棧溢出):

public static void JsonInputLogging<T>(T m, string methodName)
{
    MemoryStream ms = new MemoryStream();
    DataContractJsonSerializer ser = new 
            DataContractJsonSerializer(typeof(T));
    ser.WriteObject(ms, m);
    string jsonString = Encoding.UTF8.GetString(ms.ToArray());
    ms.Close();
    logging("MethodName: " + methodName + Environment.NewLine + jsonString.ToString());
}


public static void logging (string message)
{
    string pathLogFile = "D:\jsoninput.log";
    FileInfo jsonInputFile = new FileInfo(pathLogFile);
    if (File.Exists(jsonInputFile.ToString()))
    {
        long fileLength = jsonInputFile.Length;
        if (fileLength > 1000000)
        {
            File.Move(pathLogFile, pathLogFile.Replace(*some new path*);
        }
    }
    File.AppendAllText(pathLogFile, *some text*);
}

你必須先了解一些內部結構。 對於每個[x]用戶,ASP.Net將使用單個工作進程。 一個工作進程擁有多個線程。 如果你在雲上使用多個實例,那就更糟了,因為那時你也有多個服務器實例(我認為情況並非如此)。

這里有一些問題:

  • 您有多個用戶,因此有多個線程。
  • 多個線程可以相互死鎖寫入文件。
  • 您有多個應用程序域,因此有多個進程。
  • 多個進程可以互相鎖定

打開和鎖定文件

File.Open有一些用於鎖定的標志。 基本上你可以按進程鎖定文件,在這種情況下這是一個好主意。 使用ExistsOpen的兩步法無濟於事,因為在另一個工作進程之間可能會有所作為。 基本上,我們的想法是使用獨占訪問權限調用Open ,如果失敗,請再次嘗試使用其他文件名。

這基本上解決了多個進程的問題。

從多個線程寫

文件訪問是單線程的。 您可能希望使用單獨的線程來執行文件訪問,而使用多個線程來指示要寫入的內容,而不是將您的內容寫入文件。

如果您有比您可以處理的更多的日志請求,那么無論哪種方式,您都處於錯誤的區域。 在這種情況下,處理它以記錄IMO的最佳方法是簡單地刪除數據。 換句話說,讓記錄器有點損失,讓用戶的生活更美好。 您也可以使用隊列。

我通常使用ConcurrentQueue和一個單獨的線程來處理所有記錄的數據。

這基本上是如何做到這一點:

// Starts the worker thread that gets rid of the queue:
internal void Start()
{
    loggingWorker = new Thread(LogHandler)
    {
        Name = "Logging worker thread",
        IsBackground = true,
        Priority = ThreadPriority.BelowNormal
    };
    loggingWorker.Start();
}

我們還需要做一些事情來完成實際工作和一些共享的變量:

private Thread loggingWorker = null;
private int loggingWorkerState = 0;
private ManualResetEventSlim waiter = new ManualResetEventSlim();
private ConcurrentQueue<Tuple<LogMessageHandler, string>> queue =
    new ConcurrentQueue<Tuple<LogMessageHandler, string>>();

private void LogHandler(object o)
{
    Interlocked.Exchange(ref loggingWorkerState, 1);

    while (Interlocked.CompareExchange(ref loggingWorkerState, 1, 1) == 1)
    {
        waiter.Wait(TimeSpan.FromSeconds(10.0));
        waiter.Reset();

        Tuple<LogMessageHandler, string> item;
        while (queue.TryDequeue(out item))
        {
            writeToFile(item.Item1, item.Item2);
        }
    }
}

基本上,這段代碼使您可以使用跨線程共享的隊列來處理單個線程中的所有項目。 請注意, ConcurrentQueue不使用TryDequeue鎖,因此客戶端不會因此而感到任何痛苦。

最后需要的是將東西添加到隊列中。 這很簡單:

public void Add(LogMessageHandler l, string msg)
{
    if (queue.Count < MaxLogQueueSize) 
    {
        queue.Enqueue(new Tuple<LogMessageHandler, string>(l, msg));
        waiter.Set();
    }
}

此代碼將從多個線程調用。 這不是100%正確,因為CountEnqueue不一定必須以一致的方式調用 - 但是對於我們的意圖和目的而言,它已經足夠好了。 它也不會鎖定Enqueuewaiter會確保其他線程刪除了這些東西。

用單例模式包裝所有這些,為它添加更多邏輯,你的問題應該解決。

這可能會有問題,因為無論如何,默認情況下每個客戶端請求都由新線程處理。 您需要一些在項目中已知的“根”對象(不要認為您可以在靜態類中實現此目的),因此您可以在訪問日志文件之前鎖定它。 但請注意,它基本上會對請求進行序列化,並且可能會對性能產生非常不利的影響。

沒有多線程無法解決您的問題。 多線程如何同時寫入同一個文件? 你需要關心數據的一致性 ,我不認為這是實際的問題。

你搜索的是異步編程。 GUI無法響應的原因是,它等待任務完成。 如果你知道,記錄器是你的瓶頸,然后使用異步你的優勢。 觸發日志方法並忘記結果,只需編寫文件即可。

實際上我並不認為你的記錄器是問題。 你確定沒有其他阻止你的邏輯嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM