领域数据库异步查询和.Net
Realm database asynchronous queries and .Net
我正在开发一个字典应用程序,它有许多相互关联的表,所以当用户进行查询时,应用程序会搜索几乎所有这些表及其许多字段,这会导致对数据库的大量查询和 UI 冻结。
那么,每次用结果更新 UI 时,如何在单独的任务中异步进行这些查询?
我已经看到 findAllAsync 和类似领域的 android 版本但是对于 .net 我找不到任何替代品,我每次 [=30 时都尝试按照其他地方的建议重新初始化数据库=] 异步,但不知何故它不起作用并给我同样的错误。
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;
}
}
入门型号class:
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 "";
}
}
}
}
所以,如果有人需要这个,这就是我如何最终得到我的实际实现,它运行良好(这不是问题中的示例代码,但我认为这可能更有用和实用)- 让我知道您是否需要更多相关信息:
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();
}
这是一种在输入时进行异步搜索的方法,这就是为什么有这个 PreviousInputText
和 PreviousResults
- 避免多余的查询
在文档的 Threading 部分,它说:
Persisted instances of Realm, RealmObject, IQueryable returned from
Realm.All, or IList properties of RealmObjects can only be used on the
thread on which they were created, otherwise an exception is thrown
在您的代码中,您在 Task.Run
中创建了 Realm 实例,但试图使用它 return 之后的结果。您可以 return 您正在执行的查询的 ThreadSafeReference 并在原始线程上解析它,而不是 returning 包含 RealmObject 实例的列表:
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();
}
请将您的这行代码public async void Run()
替换为以下
public async Task Run()
只有当它是 fire and forget 方法时,你才应该将函数标记为 async void,如果你想等待它,你应该将它标记为 async Task,而不是 async void。
我正在开发一个字典应用程序,它有许多相互关联的表,所以当用户进行查询时,应用程序会搜索几乎所有这些表及其许多字段,这会导致对数据库的大量查询和 UI 冻结。
那么,每次用结果更新 UI 时,如何在单独的任务中异步进行这些查询?
我已经看到 findAllAsync 和类似领域的 android 版本但是对于 .net 我找不到任何替代品,我每次 [=30 时都尝试按照其他地方的建议重新初始化数据库=] 异步,但不知何故它不起作用并给我同样的错误。
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;
}
}
入门型号class:
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 "";
}
}
}
}
所以,如果有人需要这个,这就是我如何最终得到我的实际实现,它运行良好(这不是问题中的示例代码,但我认为这可能更有用和实用)- 让我知道您是否需要更多相关信息:
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();
}
这是一种在输入时进行异步搜索的方法,这就是为什么有这个 PreviousInputText
和 PreviousResults
- 避免多余的查询
在文档的 Threading 部分,它说:
Persisted instances of Realm, RealmObject, IQueryable returned from Realm.All, or IList properties of RealmObjects can only be used on the thread on which they were created, otherwise an exception is thrown
在您的代码中,您在 Task.Run
中创建了 Realm 实例,但试图使用它 return 之后的结果。您可以 return 您正在执行的查询的 ThreadSafeReference 并在原始线程上解析它,而不是 returning 包含 RealmObject 实例的列表:
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();
}
请将您的这行代码public async void Run()
替换为以下
public async Task Run()
只有当它是 fire and forget 方法时,你才应该将函数标记为 async void,如果你想等待它,你应该将它标记为 async Task,而不是 async void。