【问题标题】:Starting a blocking GC Explicit When Make Bulk Insert In Xamarin在 Xamarin 中进行批量插入时启动阻塞 GC 显式
【发布时间】:2019-11-19 14:03:02
【问题描述】:

我正在Xamarin 中开发我的第一个应用程序,但遇到了一个问题,我正在尝试使用我的rest apibulk insert 插入更多内容并缩短synchronization 的时间。

所以我尝试了很多方法来避免它,但都没有奏效。

使用单例连接,在创建连接时添加参数,并在dispose连接中使用using关键字。

我有 9000 个寄存器,每次获取并分页大约 1000 个寄存器。

当它到达最后一个分页页面时,它停止并且控制台给出了这样的消息。

11-19 11:52:17.453 I/art     (32524): Starting a blocking GC Explicit
11-19 11:52:17.463 I/art     (32524): Explicit concurrent mark sweep GC freed 97(4KB) AllocSpace objects, 0(0B) LOS objects, 27% free, 10MB/14MB, paused 380us total 15.150ms
11-19 11:52:17.463 D/Mono    (32524): GC_TAR_BRIDGE bridges 120 objects 126 opaque 9 colors 120 colors-bridged 120 colors-visible 120 xref 0 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.09ms tarjan 0.11ms scc-setup 0.16ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.04ms
11-19 11:52:17.463 D/Mono    (32524): GC_BRIDGE: Complete, was running for 18.02ms
11-19 11:52:17.463 D/Mono    (32524): GC_MAJOR: (LOS overflow) time 50.76ms, stw 60.27ms los size: 4096K in use: 1369K
11-19 11:52:17.463 D/Mono    (32524): GC_MAJOR_SWEEP: major size: 8208K in use: 6622K
11-19 11:52:17.693 D/Mono    (32524): GC_TAR_BRIDGE bridges 0 objects 0 opaque 0 colors 0 colors-bridged 0 colors-visible 120 xref 0 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.09ms tarjan 0.11ms scc-setup 0.16ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.00ms
11-19 11:52:17.693 D/Mono    (32524): GC_BRIDGE: Complete, was running for 0.10ms
11-19 11:52:17.693 D/Mono    (32524): GC_MINOR: (Nursery full) time 2.45ms, stw 3.05ms promoted 10K major size: 8240K in use: 6634K los size: 4096K in use: 1650K
11-19 11:52:17.933 D/Mono    (32524): GC_TAR_BRIDGE bridges 0 objects 0 opaque 0 colors 0 colors-bridged 0 colors-visible 120 xref 0 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.09ms tarjan 0.11ms scc-setup 0.16ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.00ms
11-19 11:52:17.933 D/Mono    (32524): GC_BRIDGE: Complete, was running for 0.28ms
11-19 11:52:17.933 D/Mono    (32524): GC_MINOR: (Nursery full) time 2.42ms, stw 3.03ms promoted 2K major size: 8240K in use: 6636K los size: 4096K in use: 1916K

批量插入方法

   public void BulkInsert(JArray array, string tableName = "")
    {
        try
        {
            if (string.IsNullOrEmpty(tableName))
            {
                Type typeParameterType = typeof(T);
                tableName = typeParameterType.Name;
            }

            StringBuilder stringBuilder = new StringBuilder();

            array.ToList().ForEach(register =>
            {
                stringBuilder.AppendLine(DataBaseUtil.GenerateInsertStatement(register, tableName));
            });

            SQLiteCommand command = new SQLiteCommand(ConnectionDataBase.Connection);
            command.CommandText = stringBuilder.ToString();
            command.ExecuteNonQuery();

        }
        catch (Exception e)
        {
            LogUtil.WriteLog(e);
        }


public static string GenerateInsertStatement(JToken register, string tableName)
{
    var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(register.ToString());
    string columns = string.Join(",", data.Keys.ToList());
    string values = string.Join(",", data.Values.Select(v => string.Format(@"'{0}'", v.Trim())));
    return string.Format("INSERT INTO {0} ({1}) VALUES ({2}); ", tableName, columns, values);
}

处理这么多记录的方法

private async Task GenerateOrdensServico()
{
    try
    {
        _logs.Add("ORDENS DE SERVIÇO");

        double increment = ((1 - Progresso) / 2);
        int records = await _ordemServicoRest.GetCount();
        int limit = _sistemaParametroRepository.GetTamanhoPaginaSincMobile();
        int pages = (records / limit);

        for (int i = 0; i <= pages; i++)
        {
            JArray ordensServico = await _ordemServicoRest.GetAllInJsonFormatPaginated(DataBaseUtil.GetPagination(i, limit));

            if (ordensServico == null)
            {
                _logs.Add("Não Contem O.S de Corte para importar!");
                await App.Current.MainPage.DisplayAlert("Atenção", "Não tem O.S para importar!", "OK");
                continue;
            }

            _ordemServicoRepository.BulkInsert(ordensServico);
            AlterProgress(Progresso += ((Progresso * 100) + (increment / pages)));
        }
    }
    catch (Exception e)
    {
        LogUtil.WriteLog(e);
    }
}

我的http get方法

protected async Task<Object> GetValue(string metodo = "", string parametros = "")
{
    try
    {
        _urlBase = new Uri(GetStringConnectionParameters(metodo) + parametros);

        using (HttpClient client = new HttpClient())
        using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, _urlBase))
        using (HttpResponseMessage response = await client.SendAsync(request))
        {
            Stream content = await response.Content.ReadAsStreamAsync();

            if (response.IsSuccessStatusCode)
            {
                return DeserializeJsonFromStream<Object>(content);
            }
        }
    }
    catch (Exception e)
    {
        LogUtil.WriteLog(e);
    }

    return null;
}

以及调用我需要插入数据库的实体列表的方法

    private async Task RegistrarDados()
    {
        try
        {
            _logs.Add("Realizando o Download: ");

            AlterProgress(12.5);
            await GenerateAtendimentoMotivosEncerramento();

            AlterProgress(15);
            await GenerateHidrometrosLocalInstalacao();

            AlterProgress(17.5);
            await GenerateHidrometrosProtecao();

            AlterProgress(20);
            await GenerateFuncionarios();

            AlterProgress(25);
            await GenerateGrupoFaturamento();

            AlterProgress(30);
            await GenerateLigacaoAguaSituacoes();

            AlterProgress(40);
            await GenerateLigacaoEsgotoSituacoes();

            AlterProgress(45);
            await GenerateServicosTipo();

            AlterProgress(50);
            await GenerateSistemParametros();

            AlterProgress(55);
            await GenerateOrdensServico();

            AlterProgress(55);
            await GenerateContas();

            int contador = _ordemServicoRepository.Count<OrdemServico>();

            AlterProgress(100);
            _logs.Add("Sincronização encerrada com sucesso!");
            await App.Current.MainPage.DisplayAlert("Atenção", "Foram importados " + contador + " Ordens de Serviços!", "OK");
            PodeSincronizar = true;
        }
        catch (Exception e)
        {
            LogUtil.WriteLog(e);
        }
    }

方法GenerateSistemParametros(); GenerateOrdensServico(); GenerateContas();需要按此顺序执行,因为它们有依赖关系。

编辑

我也先尝试了这个方法保存

public void BulkInsert(JArray array, string tableName = "")
{
    try
    {
        if (string.IsNullOrEmpty(tableName))
        {
            Type typeParameterType = typeof(T);
            tableName = typeParameterType.Name;
        }

        using (SQLiteConnection connection = new SQLiteConnection(DataBaseUtil.GetDataBasePath()))
        {
            connection.BeginTransaction();

            array.ToList().ForEach(register =>
            {
                string sql = DataBaseUtil.GenerateInsertStatement(register, tableName);
                System.Diagnostics.Debug.WriteLine(sql);
                var command = connection.CreateCommand(sql);
                command.ExecuteNonQuery();
            });

            connection.Commit();
            DataBaseUtil.CloseConnection(connection);
        }
    }
    catch (Exception e)
    {
        LogUtil.WriteLog(e);
    }
}

【问题讨论】:

  • 根据来自控制台的消息,您的应用程序内存不足(OOM),android OS 为您的应用程序执行 GC。我检查了您的代码,如果您一一插入记录,它将导致硬盘IO很多,所以我建议你可以尝试使用Transaction这样的链接。stackoverflow.com/a/8163179/10627299
  • @LeonLu-MSFT 感谢您的回复,我也尝试使用 begin 和 commit 方法但没有成功。
  • 您是否尝试减少插入记录,如果您会遇到这个问题?
  • 我在控制台日志中没有看到任何远程 OOM。您确定问题出在 SQLite 上并且没有收到数据吗?尝试使用秒表并记录每次外部操作前后的时间。
  • 我收到数据是的,我在console.log打印了查询

标签: c# sqlite xamarin


【解决方案1】:

关于HttpClient使用的观察

参考You're using HttpClient wrong and it is destabilizing your software

private static Lazy<HttpClient> client = new Lazy<HttpClient>();

protected async Task<Object> GetValue(string metodo = "", string parametros = "") {
    try {
        _urlBase = new Uri(GetStringConnectionParameters(metodo) + parametros);                        
        using (HttpResponseMessage response = await client.Value.GetAsync( _urlBase)) {               
            if (response.IsSuccessStatusCode) {
                Stream content = await response.Content.ReadAsStreamAsync();
                return DeserializeJsonFromStream<Object>(content);
            }
        }
    } catch (Exception e) {
        LogUtil.WriteLog(e);
    }    
    return null;
}

【讨论】:

  • @DiegoMacario 您是否在进行任何可能导致死锁的阻塞调用,例如 .Result.Wait()
  • 我提出了您的建议,但没有奏效,仍然出现 GC 错误
【解决方案2】:

我建议遵循SQLite.NET Documentation 中“优化 SQL 查询”部分中的指示。

请阅读该文件,它写得很好。 重点推荐:

  1. 将所有插入包装在一个事务中(如前所述)
  2. 使用参数化查询(这始终是一种很好的做法,因为它保证特殊字符将被正确转义)
  3. 对每个查询使用一个插入语句,而不是尝试将其中的许多语句填充到一个语句中。
  4. 避免不必要的内存分配

【讨论】:

  • 1 - Yes, you are right 2 - Sqlite in Xamarin doesn´t offer suport to parameters, I didn´t found 3 - When I send the data to App, I remove null values 4 - I don´t know what you are talking, I use singleton in connection, in repository classes
  • 您使用的是哪个 SQLite 包?
  • sqlite-net-pcl
  • 好的,所以这是一个与我预期不同的库。无论如何,尝试对每个查询进行一次插入。此外,您可以尝试启用调试库代码以更好地了解执行卡住的位置。在 Visual Studio for Mac 中,选项位于 Preferences > Projects > Debugger > “Debug project code only; do not step into framework code”,取消勾选。
  • 我查看了我的查询,问题就在那里。
猜你喜欢
  • 1970-01-01
  • 2020-09-30
  • 2015-11-18
  • 1970-01-01
  • 1970-01-01
  • 2011-07-28
  • 1970-01-01
  • 2021-09-14
相关资源
最近更新 更多