多个用户在同一个文件上写入

wul*_*try 8 .net c# multithreading

我有一个项目是一个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*);
}
Run Code Online (Sandbox Code Playgroud)

atl*_*ste 5

你必须先了解一些内部结构.对于每个[x]用户,ASP.Net将使用单个工作进程.一个工作进程拥有多个线程.如果你在云上使用多个实例,那就更糟了,因为那时你也有多个服务器实例(我认为情况并非如此).

这里有一些问题:

  • 您有多个用户,因此有多个线程.
  • 多个线程可以相互死锁写入文件.
  • 您有多个应用程序域,因此有多个进程.
  • 多个进程可以互相锁定

打开和锁定文件

File.Open有几个锁定标志.基本上你可以按进程锁定文件,在这种情况下这是一个好主意.有一个两步走的方法Exists,并Open没有帮助,因为在其它工作进程之间可能会做一些事情.基本上,这个想法是Open使用write-exclusive访问进行调用,如果失败,请再次尝试使用其他文件名.

这基本上解决了多个进程的问题.

从多个线程写

文件访问是单线程的.您可能希望使用单独的线程来执行文件访问,而使用多个线程来指示要写入的内容,而不是将您的内容写入文件.

如果您有比您可以处理的更多的日志请求,那么无论哪种方式,您都处于错误的区域.在这种情况下,处理它以记录IMO的最佳方法是简单地删除数据.换句话说,让记录器有点损失,让用户的生活更美好.您也可以使用队列.

我通常使用a 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();
}
Run Code Online (Sandbox Code Playgroud)

我们还需要做一些事情来完成实际工作和一些共享的变量:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上,这段代码使您可以使用跨线程共享的队列来处理单个线程中的所有项目.请注意,ConcurrentQueue不使用锁TryDequeue,因此客户不会因此而感到任何痛苦.

最后需要的是将东西添加到队列中.这很简单:

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

此代码将从多个线程调用.这不是100%正确的,因为CountEnqueue不必以一致的方式被称为-但我们的意图和目的已足够好了.它也没有锁定,Enqueue并且waiter将确保其他线程删除的东西.

用单例模式包装所有这些,为它添加更多逻辑,你的问题应该解决.