【问题标题】:Async Data Reading/Parsing Performance异步数据读取/解析性能
【发布时间】:2017-11-18 21:52:40
【问题描述】:

我正在编写代码来加载和解析游戏中的数据文本。有很多分支读取器调用 - 例如,地图将包含道具和敌人等数据,并且这些对象有自己的文件被调用和读取。这通常会很快加载,大约 1.5 秒,但在速度较慢的机器上,它可能需要大约 5 秒以上,并导致游戏窗口在完成之前无响应。

现在我正在研究如何让窗口保持活动状态,同时仍然保持较短的加载时间。我已经将一些加载分离到在主线程后台运行的任务中,然后当加载完成时,它会告诉主线程切换状态并继续游戏。这可行,但是,我的加载时间从 1.5 秒变为 53 秒。切换到这样的后台任务时这是正常的性能吗?我发布了一些通用代码作为当前处理方式的示例。

Map map = null;
//Main Update Loop
public void Update()
{
    if(GameState == Active)
       map.Update();
    else
       ShowLoadingScreen();
}

//LoadWorld gets called from elsewhere, like a UI
public async void LoadWorld()
{
   GameState = State.Loading;
   await Task.Run(() => { LoadFile("mapdata", out map); });
   GameState = State.Active;
   map.Start();
}

//This loads the file and reads the first line
//which tells the reader what sort of object it is
public void LoadFile(String file, out Map m)
{
   m = new Map(); //create new map
   StreamReader sr = new StreamReader(file);
   String line;
   Object obj = null;
   while ((line = sr.ReadLine()) != null) 
   {
      switch(line)
      {
         case "A":
           obj = parseObjectA(line, sr); //continues with its own loop
           break;
         case "B":
           obj = parseObjectB(line, sr); //continues with its own loop
           break;
      }
      map.addObject(obj);
   }
}

//This loops through the reader and fills an object with data, then returns it
public Object parseObjectA(String line, StreamReader sr)
{
   Object obj = new Object();
   while ((line = sr.ReadLine()) != null) 
   {
      String element;
      String value;
      //parseLine is a function that breaks apart the line into an element and value
      parseLine(line, out element, out value);
      switch(element)
      {
         case "name":
            obj.Name = value;
            break;
         case "position":
            {
               int pos = 0;
               Int32.TryParse(value, out pos);
               obj.position = pos;
               break;
            }
      }
   }
   return obj;
}

【问题讨论】:

  • 在我看来,您可以在 LoadFile 方法中使用 StreamReader 中的 async 方法。还可以考虑将流阅读器的使用情况包装在 using 块中。我会继续阅读,看看是否还有其他可能影响性能的因素。
  • @Fabulous 根据我之前的说法,ReadLine 不会阻塞线程,因为它是一个 I/O 操作,而且 ReadLineAsync 可能会进一步降低性能,因为实际读取不需要异步完成,因此使用 ReadLineAsync 会增加不必要的开销。这些是真的吗?
  • 如果您正在对返回的数据进行一些重要的处理,则差异可能可以忽略不计。考虑解决该问题的this thread。那里的问题还有其他方面,但您的问题已得到解决。通常async/await 调用一直沿调用堆栈向下级联。在这种情况下,您可能需要查看 parseLine 操作中的代码。
  • @Fabulous parseLine 只是将字符串拆分为我可以在开关中使用的值。因此,如果该行看起来像“name”“Joe”,我将返回 'name' 作为元素,并返回 'Joe' 作为值。我认为在代码示例中添加并不重要。
  • 加载文件时通常处理多少行?是否可以并行处理这些行?如果一条线没有直接受到另一条线的影响,您可能需要调查一下。如果没有运行代码的能力,就不可能查明确切的原因。您可能会从并行化中受益。

标签: c# multithreading asynchronous io monogame


【解决方案1】:

我会将此作为评论发布,但我还不允许,因此将其扩展为更全面的解释。

从游戏开发的角度来看,我会将加载程序创建为后台工作线程。这将使窗口的主线程/窗体响应用户输入。

在 backgroundWorker 中,您可以根据需要启动单独的任务来完成单独的工作负载,但 UI 始终是响应式的。

要记住的重要一点是在类的根目录中创建所需的变量,然后在后台线程中填充它们。这样做意味着您可以在主线程中完成所有操作后轻松读取结果。

举个例子:

class MainThread
{
    BackgroundLoader bgLoader;

    public MainThread()
    {
        bgLoader = new BackgroundLoader();
    }

    public void LoadItems()
    {
        bgLoader.Load();
    }
}

class BackgroundLoader
{
    BackgroundWorker backgroundWorker = new BackgroundWorker();
    // create the variables to hold the results.
    Map map;

    public void Load()
    {
        backgroundWorker.WorkerReportsProgress = true;
        backgroundWorker.WorkerSupportsCancellation = true;
        backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
        backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
        backgroundWorker.DoWork += BackgroundWorker_DoWork;
        backgroundWorker.RunWorkerAsync();
        while (!backgroundWorker.IsBusy) { }
    }

    private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        // This is where you do the work and load your variables, which you then access once the thread is complete.
        map = LoadMap();
    }
}

【讨论】:

  • 谢谢!我不确定这是否能解决我的问题,但绝对值得研究。
  • 当您在从主线程运行任务时遇到任务速度变慢的情况,您可以尝试像以前一样单线程运行整个负载堆栈,但完全在后台线程中。这可能会消除您看到的问题。
猜你喜欢
  • 2016-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多