【问题标题】:Realm database asynchronous queries and .Net领域数据库异步查询和.Net
【发布时间】:2020-05-04 19:32:32
【问题描述】:

我正在开发一个字典应用程序,它具有许多相互连接的表,因此当用户进行查询时,应用程序会搜索几乎所有这些表及其许多字段,这会导致对数据库的大量查询和 UI 冻结。

那么,如何在每次使用结果更新 UI 时异步地在单独的任务中进行这些查询?

我已经看到了适用于 android 版本领域的 findAllAsync 等,但对于 .net,我找不到任何替代方案,我每次运行异步时都尝试按照其他地方的建议重新初始化数据库,但不知何故它没有t 工作并给我同样的错误。

System.Exception: 'Realm accessed from incorrect thread.'

当我尝试将领域结果转换为普通列表以在 UI 上处理时,在 ToList() 上引发错误,请帮助解决此问题

这是我的代码

using Data.Models;
using Microsoft.EntityFrameworkCore.Internal;
using Realms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Data
{
   public class RealmAsync
{
    IQueryable<Entry> Entries { get; set; }

    public async void Run()
    {
        Realm RealmDatabase = Realm.GetInstance();
        Entries = RealmDatabase.All<Entry>();

        var entries = await FindAsync("а", Entries);
        Console.WriteLine($"async result async {entries.Count()}");
    }
    public async Task<IEnumerable<Entry>> FindAsync(string needle, IQueryable<Entry> haystack)
    {
        var foregroundRealm = Realm.GetInstance();
        var haystackRef = ThreadSafeReference.Create<Entry>(haystack);

        var filteredEntriesRef = await Task.Run(() =>
        {
            using (var realm = Realm.GetInstance())
            {
                var bgHaystack = realm.ResolveReference(haystackRef);
                return ThreadSafeReference.Create(bgHaystack.Where(entry => entry.Content.ToLower().StartsWith(needle)));
            }
        });

        var result = foregroundRealm.ResolveReference(filteredEntriesRef).ToArray();
        return result;
    }
}

入门模型类:

using System.ComponentModel.DataAnnotations.Schema;
using Realms;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System;

namespace Data.Models
{
    [Table("entry")]
    public class Entry : RealmObject
    {
        public class EntryType
        {
            public const byte Word = 1;
            public const byte Phrase = 2;
            public const byte Text = 3;
        };

        [Key]
        [PrimaryKey]
        [Column("entry_id")]
        public int Id { get; set; }

        [Column("user_id")]
        public int UserId { get; set; }

        [Column("source_id")]
        public int SourceId { get; set; }

        [Indexed]
        [Column("type")]
        public byte Type { get; set; }

        [Column("rate")]
        public int Rate { get; set; }

        [Column("created_at")]
        public string CreatedAt { get; set; }

        [Column("updated_at")]
        public string UpdatedAt { get; set; }

        [NotMapped]
        public Phrase Phrase { get; set; }

        [NotMapped]
        public Word Word { get; set; }

        [NotMapped]
        public Text Text { get; set; }

        [NotMapped]
        public IList<Translation> Translations { get; }

        [NotMapped]
        public string Content
        {
            get {
                switch (Type)
                {
                    case EntryType.Phrase:
                        return Phrase?.Content;
                    case EntryType.Word:
                        return Word?.Content;
                    case EntryType.Text:
                        return Text?.Content;
                }
                return "";
            }
        }
    }
}

【问题讨论】:

  • 我的建议:在Resultawait 之间进行选择。使用其中之一,而不是两者都使用。否则……死锁。如果您决定使用后者,avoid async void.
  • @TheodorZoulias 我简化了代码库,请检查您是否可以提供帮助,我认为这与 Realm 的工作方式更相关
  • 现在没有Result,因此可能出现死锁的主要嫌疑人已被消除。您得到的错误意味着存在线程关联。您可能会发现这个问题很有用:Forcing certain code to always run on the same thread.

标签: c# .net asynchronous async-await realm


【解决方案1】:

在文档的Threading 部分中,它说:

从返回的 Realm、RealmObject、IQueryable 的持久化实例 Realm.All 或 RealmObjects 的 IList 属性只能用于 创建它们的线程,否则抛出异常

在您的代码中,您在Task.Run 内创建了Realm 实例,但在它返回后尝试使用结果。除了返回包含 RealmObject 实例的列表之外,您还可以将 ThreadSafeReference 返回到您正在执行的查询并在原始线程上解决它:

public async Task<IEnumerable<Entry>> FindAsync(string needle, IQueryable<Entry> haystack)
{
    var foregroundRealm = Realm.GetInstance();
    var haystackRef = haystack;
    var filteredEntriesRef = await Task.Run(() =>
    {
        using (var realm = Realm.GetInstance())
        {
            var bgHaystack = realm.ResolveReference(haystackRef);
            return ThreadSafeReference.Create(bgHaystack.Where(entry => entry.Content.ToLower().StartsWith(needle)));
        }
    });

    return foregroundRealm.ResolveReference(filteredEntriesRef).ToArray();
}

【讨论】:

  • 感谢您的帮助,似乎它应该工作,但它没有,应用程序在到达 return foregroundRealm.ResolveReference(filteredEntriesRef).ToArray(); 之前停止执行- 没有错误
  • 执行停止在使用 (var realm = Realm.GetInstance()) 行,请检查它是否适合您,以及浅薄的经验,我可以查询的.net 领域信息很少
【解决方案2】:

所以,如果有人需要这个,这就是我最终得到我的真正实现的方式,它运行良好(这不是问题中的示例代码,但我认为这可能更有用和实用) - 让我知道是否您将需要有关它的更多信息:

 public async override Task<List<Entry>> FindAsync(string inputText)
        {
            // Checks for previous results to speed up the search while typing
            // For realm to be able to work cross-thread, when having previous results this has to be passed to the search task
            ThreadSafeReference.Query<Entry> previousResultsRef = null;
            if (PreviousInputText != null)
            {
                if ((PreviousInputText.Length == inputText.Length - 1) && (inputText == PreviousInputText + inputText.Last()))
                {
                    // Characters are being inserted
                    if (PreviousResults.ContainsKey(PreviousInputText.Length - 1))
                    {
                        previousResultsRef = ThreadSafeReference.Create(PreviousResults[PreviousInputText.Length - 1]);
                    }
                } else if ((PreviousInputText.Length == inputText.Length + 1) && (inputText == PreviousInputText.Substring(0, PreviousInputText.Length - 1)))
                {
                    // Characters are being removed
                    PreviousResults[PreviousInputText.Length] = null;
                    PreviousInputText = inputText;
                    return PreviousResults[PreviousInputText.Length - 1].ToList();
                }
            }

            // Receives reference to the search results from the dedicated task
            var resultingEntries = await Task.Run(() =>
            {
                // Preserves current input text for the further uses
                PreviousInputText = inputText;

                // Gets the source entries - either previous or all
                var sourceEntries = (previousResultsRef == null) ? Realm.GetInstance(DbPath).All<Entry>() : Realm.GetInstance(DbPath).ResolveReference(previousResultsRef);

                // Because realm doesn't support some of the LINQ operations on not stored fields (Content) 
                // the set of entries @sourceEntries is assigned to a IEnumerable through conversion when passing to Search method as a parameter
                IEnumerable<Entry> foundEntries = Search(inputText, sourceEntries);

                // Extracts ids
                var foundEntryIds = ExtractIdsFromEntries(foundEntries);

                if (foundEntryIds.Count() == 0)
                {
                    return null;
                }

                // Select entries
                var filteredEntries = FilterEntriesByIds(sourceEntries, foundEntryIds.ToArray());

                if (filteredEntries == null)
                {
                    // Something went wrong
                    return null;
                }

                // Creates reference for the main thread
                ThreadSafeReference.Query<Entry> filteredEntriesRef = ThreadSafeReference.Create(filteredEntries);
                return filteredEntriesRef;
            });

            if (resultingEntries == null)
            {
                // Next search will be a new search
                PreviousResults[inputText.Length - 1] = null;
                return new List<Entry>();
            }

            var results = RealmDatabase.ResolveReference(resultingEntries);

            // Preserve the current results and the input text for the further uses
            PreviousInputText = inputText;
            if (PreviousResults.ContainsKey(inputText.Length - 1))
            {
                PreviousResults[inputText.Length - 1] = results;
            } else
            {
                PreviousResults.Add(inputText.Length - 1, results);
            }

            return Sort(results, inputText).ToList();
        }

这是一种在打字时进行异步搜索的方法,这就是为什么会有 PreviousInputTextPreviousResults - 避免多余的查询

【讨论】:

    【解决方案3】:

    请将您的这行代码public async void Run() 替换为以下内容

    public async Task Run()
    

    你应该标记函数 async void 只有当它是 fire and forget 方法时,如果你想等待它,你应该将它标记为 async Task,而不是 async void。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-11
      • 1970-01-01
      相关资源
      最近更新 更多