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

时间:2015-07-29 06:02:44

标签: c# .net 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*);
}

3 个答案:

答案 0 :(得分:5)

你必须首先了解一些内部结构。对于每个[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将确保其他线程删除这些内容。

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

没有多线程无法解决您的问题。多线程如何同时写入同一个文件?您需要关注数据一致性,我认为这不是实际问题。

您搜索的是异步编程。 GUI无法响应的原因是,它等待任务完成。如果你知道,记录器是你的瓶颈,然后使用异步你的优势。触发日志方法并忘记结果,只需编写文件即可。

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