从错误的线程访问Realm - Swift 3

4

在我的UITableViewController的顶部是以下内容:

let queue = DispatchQueue(label: "background")

当任务被删除时,执行以下操作:

self.queue.async {
    autoreleasepool {
        let realm = try! Realm()
        realm.beginWrite()
        realm.delete(task)
        do {
            try realm.commitWrite()
        } catch let error {
            self.presentError()
        }
    } 
 }

然后我收到了以下错误信息:

由于在错误的线程中访问 Realm,程序终止并抛出了 realm::IncorrectThreadException 异常。

我该如何解决这个问题?

4个回答

6
似乎写操作在与最初访问对象不同的线程上执行。你可以通过传递任务的id并在异步块内进行写操作之前使用它从数据库中获取它来修复它。
因此,在顶部:
var taskId = 0  // Set this accordingly

并且接下来会有类似这样的内容。
self.queue.async {
    autoreleasepool {
        let realm = try! Realm()
        let tempTask = // get task from Realm based on taskId
        realm.beginWrite()
        realm.delete(tempTask)
        do {
            try realm.commitWrite()
        } catch let error {
            self.presentError()
        }
    } 
 }

啊,那么如果我想要写入,就不能从主线程(UI 线程)读取了吗?那我是不是应该重新考虑是否使用线程呢? - AppreciateIt
1
是的,关于 Realm 在对象在线程之间传递时崩溃的问题,我经常遇到 :) - Samantha
你认为如果我还需要进行额外的读取,使用线程编写会有什么优势吗? - AppreciateIt
我认为这取决于个人偏好和/或您的应用程序。如果在主线程上执行所有删除操作会减慢UI速度,那么我可以看到使用异步块...但是,如果它不会真正影响应用程序的响应能力,那么将所有操作保持在一个线程上可能会更简单(并且少一些数据库操作)。 - Samantha
1
谢谢!这是我们推荐将工作转移到后台线程的方式。您可以将 taskId 设置为 Realm 对象的主键,然后通过使用 Realm.object(ofType:forPrimaryKey:) 在后台线程检索到对它的引用。 - TiM
1
作为一个经验法则,我们建议尽可能将长时间的 Realm 写入事务转移到后台线程。虽然可以在写操作期间在其他线程上继续从 Realm 读取数据,但需要注意,如果后台正在进行写入,并且您尝试在主线程上开始写入,则会阻塞用户界面,直到后台操作完成。 - TiM

4

我们需要明白一个事实,Realm对象无法从不同的线程访问。这意味着什么以及如何解决这个问题。

首先,Realm对象不能从不同的线程访问,这意味着在一个线程中定义的Realm实例不能从不同的线程访问。实际上,我们需要为每个线程拥有不同的realm实例。

例如,让我们看以下示例,在单击按钮时在后台线程异步插入50条记录到数据库中,并在主线程中添加通知块以更新计数标签中的人数。每个线程(主线程和后台线程)都有自己的realm对象实例来访问Realm数据库。因为Realm数据库通过使Realm实例受限于线程来实现线程安全。

class Person: Object {
    dynamic var name = ""
    convenience init(_ name: String) {
        self.init()
        self.name = name
    }
}


override func viewDidAppear(_ animated: Bool) {
    let realmMain = try!  Realm ()
    self.people = realmMain.objects(Person.self)
    self.notification = self.people?.addNotificationBlock{ [weak self] changes in
        print("UI update needed")
        guard let countLabel = self?.countLabel else {
            return
        }
        countLabel.text = "Total People: \(String(describing: self?.people?.count))"
    }
}

@IBAction func addHandler(_ sender: Any) {
    print(#function)
    let backgroundQueue = DispatchQueue(label: "com.app.queue",
                                        qos: .background,
                                        target: nil)



    backgroundQueue.async {
        print("Dispatched to background queue")
        let realm = try! Realm()
        try! realm.write {
            for i in 1..<50 {
                let name = String(format: "rajan-%d", i)
                //print(#function, name)
                realm.add(Person(name))
            }
        }

    }
}

0

另外,您也可以获得

Realm accessed from incorrect thread

如果你尝试按获取的项来写作


-1

你也可以使用ThreadSafe引用,这是一种在线程之间传递realm对象的特定方式:

let realm = try! Realm()
let person = Person(name: "Jane") // no primary key required 
try! realm.write {
    realm.add(person)
}
let personRef = ThreadSafeReference(to: person)
DispatchQueue(label: "com.example.myApp.bg").async {
    let realm = try! Realm()
    guard let person = realm.resolve(personRef) else {
    return // person was deleted
}
try! realm.write {
    person.name = "Jane Doe"
}

Realm文档提供的步骤:

  1. 使用线程限制对象初始化ThreadSafeReference。
  2. 将该ThreadSafeReference传递到目标线程或队列。
  3. 通过调用Realm.resolve(_ :)在目标Realm上解析此引用。
  4. 像往常一样使用返回的对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接