Threading and Concurrency
The EmSACAN API supports multiple connected CAN Dragon devices and multi-threaded applications when you follow the rules below. These rules apply equally to the C API and the C# wrapper (which calls the same native library).
Multiple Devices
More than one CAN Dragon may be connected simultaneously. Each successful EmSACAN_Connect / CanDragonDevice.Connect returns an independent session handle. Use one handle (or CanDragonDevice instance) per physical device.
using (CanDragonDevice a = CanDragonDevice.Connect(serialA, false))
using (CanDragonDevice b = CanDragonDevice.Connect(serialB, false))
{
/* each device has its own handle and port state */
a.OpenChannel(0);
b.OpenChannel(0);
}
What Is Supported
| Scenario | Supported |
|---|---|
| Read, Write, GetStatus, OpenChannel, CloseChannel, ResetChannel on different handles from different threads | Yes |
| One dedicated thread per device for ongoing CAN I/O | Yes (recommended) |
| EmSACAN_CalculateTiming / EmSACANLibrary.CalculateTiming from any thread | Yes (no session handle) |
What Is Not Supported
| Scenario | Supported |
|---|---|
| Calling any function on the same handle from multiple threads without your own lock | No |
Concurrent Startup, Shutdown, EnumerateInterfaces, Connect, or Disconnect without coordination | No |
| More than one firmware update active in the process | No |
| Calling EmSACAN functions on a handle from that handle’s firmware-update callback | No |
| Calling EmSACAN functions on a handle from that handle’s port connection callback | No |
::caution Sharing one CanDragonDevice or emsacan_handle_t across threads without a mutex can cause intermittent failures or corrupted state. Assign one owner thread per device, or guard all calls with a lock. ::
Per-Handle Limitations
Firmware update: While an update is active on a handle, other calls on that handle may return EMSACAN_ERR_FWUPDATE_ACTIVE or EMSACAN_ERR_BUSY. Only one update may run in the entire process.
Parameter read/write: EmSACAN_ReadParameter and EmSACAN_WriteParameter block until the device responds. Do not overlap read and write parameter calls on the same handle from different threads unless you serialize them.
Error reporting: EmSACAN_GetLastError(NULL) / EmSACANLibrary.GetLastError() reports the last process-wide error. With a non-null handle it reports the last error for that session only.
Recommended Patterns
Single-threaded application
The simplest approach: all API calls run on the main thread. Poll Read in your main loop or use SetCANEvents to wake a single thread.
One thread per device
For multiple CAN Dragon units, dedicate one thread to each CanDragonDevice:
EmSACANLibrary.Startup();
var list = EmSACANLibrary.EnumerateInterfaces(2);
CanDragonDevice devA = CanDragonDevice.Connect(list[0].SerialNumber, false);
CanDragonDevice devB = CanDragonDevice.Connect(list[1].SerialNumber, false);
Task txA = Task.Run(() => { /* only devA calls here */ });
Task txB = Task.Run(() => { /* only devB calls here */ });
Task.WaitAll(txA, txB);
devA.Dispose();
devB.Dispose();
EmSACANLibrary.Shutdown();
Connection and firmware callbacks
USB presence callbacks (ChannelConnectionChanged in C#, emsacan_channel_connection_callback_fn in C) run on a native monitor thread. Keep callback bodies short—set a flag or signal an event, but do not call back into EmSACAN on the same handle from within the callback.
Firmware progress callbacks may run on a worker thread. Update UI via Invoke / a thread-safe queue, and do not call EmSACAN on that handle from inside the callback.
Startup and Shutdown Coordination
Call Startup once before spawning worker threads that use the API. Call Shutdown only after all worker threads have finished and all devices are disconnected.
::note The C# wrapper serializes Startup and Shutdown internally with a lock, but you must still avoid calling Connect or EnumerateInterfaces concurrently from multiple threads without your own coordination. ::
Summary
| Rule | Action |
|---|---|
| One handle per device | Connect each CAN Dragon separately |
| One owner per handle | Single thread or mutex per session |
| Lifecycle on main thread | Startup, enumerate, connect, shutdown |
| Callbacks are lightweight | Signal events; no re-entrant API calls |
| One firmware update at a time | Wait for completion before starting another |
