JetForMe
(Rick M)
1
Forgive me if this is obvious, but I'm very new to async/await. If this topic is covered in some resource online, I'd greatly appreciate a pointer to it.
I'm writing a Swift wrapper around libmodbus, which I'm currently calling like this:
public
func
readRegister(address inAddr: Int, fromDevice inDeviceID: Int, completion inCompletion: @escaping (UInt16?, Error?) -> ())
{
self.workQ.async
{
do
{
self.deviceID = inDeviceID
let r = try self.readRegister(address: inAddr)
self.callbackQ.async { inCompletion(r, nil) }
}
catch (let e)
{
self.callbackQ.async { inCompletion(nil, e) }
}
}
}
func
readRegister(address inAddr: Int)
throws
-> UInt16
{
if self.deviceID == -1
{
throw MBError.deviceIDNotSet
}
var v: UInt16 = 0
let rc = modbus_read_registers(self.ctx, Int32(inAddr), 1, &v)
if rc != 1
{
throw MBError(errno: errno)
}
return v
}
The libmodbus structure is only ever accessed on the workQ, and the callbacks always happen on the callbackQ. Other than that, each individual libmodbus call blocks until it completes (or times out).
What's the best way to wrap a library like this in a new async/await-style API?
A simple but slightly suboptimal solution would be to just wrap the whole thing in an actor. Blocking actor threads isn't great, but blocking just one actor thread is generally safe enough (I guess it's a problem if you ever ship on a single core device).
More robust would be to use withCheckedThrowingContinuation with a callback queue like you currently have.
Eventually, once we have custom executors, that would be a way to fix the downsides of the first approach.
3 Likes
JetForMe
(Rick M)
3
To close the loop on this, this is how I ended up implementing it:
public
func
readRegister(fromDevice inDeviceID: Int, atAddress inAddr: Int)
async
throws
-> UInt16
{
try await withCheckedThrowingContinuation
{ inCont in
self.workQ.async
{
do
{
self.deviceID = inDeviceID
let r = try self.readRegister(address: inAddr)
inCont.resume(returning: r)
}
catch (let e)
{
inCont.resume(throwing: e)
}
}
}
}
Annoyingly, Void return types needed explicit typing:
public
func
write(toDevice inDeviceID: Int, atAddress inAddr: Int, value inVal: Float)
async
throws
{
try await withCheckedThrowingContinuation
{ (inCont: CheckedContinuation<Void, Error>) -> Void in
self.workQ.async
{
do
{
self.deviceID = inDeviceID
try self.write(address: inAddr, value: inVal)
inCont.resume()
}
catch (let e)
{
inCont.resume(throwing: e)
}
}
}
}
1 Like