多个用户写入同一个文件

本文关键字:同一个 文件 用户 | 更新日期: 2023-09-27 18:05:28

我有一个项目,这是一个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不一定要以一致的方式调用-但对于我们的意图和目的来说,它已经足够好了。它也不会锁定Enqueue, waiter将确保其他线程删除这些东西。

将所有这些封装在一个单例模式中,再添加一些逻辑,您的问题应该就解决了。

这可能会有问题,因为默认情况下每个客户端请求都由新线程处理。您需要一些在整个项目中已知的"根"对象(不要认为您可以在静态类中实现这一点),因此您可以在访问日志文件之前锁定它。但是,请注意,它基本上将请求序列化,并且可能会对性能产生非常糟糕的影响。

多线程不能解决您的问题。如何多个线程应该写同一个文件在同一时间?你需要关心数据的一致性,我不认为这是真正的问题。

您搜索的是异步编程。GUI变得无响应的原因是,它等待任务完成。如果您知道日志记录器是您的瓶颈,那么使用async对您有利。触发log方法,忘掉结果,只写文件。

实际上我不认为你的记录器是问题所在。你确定没有其他的逻辑阻碍你吗?