Busy Tokens - AngryCarrot789/MemoryEngine360 GitHub Wiki
What are busy tokens?
MemoryEngine360 is designed to be multithreaded, but since connections are not thread safe, there exists a BusyToken to synchronize access to connections.
In the status bar or in a popup you might see "Waiting for busy operations...", which means an operation is trying to run that requires the connection, but something else is currently using it.
The busy tokens are taken via the BusyLock class. Current users include:
MemoryEngineclass, because anything can use its connection.ConsoleDebuggerclass, because it has its own dedicated connection usable by many features like thread refresh, register refresh and memory refresh).Scriptclass, because it allows (optionally) a dedicated connection instead of using the engine connection, so its busy lock is referred to as "DedicatedBusyLock". When not using a dedicated connection, it falls back to the engine's connection.
Task Sequences do not use a dedicated lock despite supporting a dedicated connection, because each sequence can have its own dedicated connection, therefore no one else can use it, so locking is not required. By default, it uses the engine's connection, so it will use the engine's busy lock.
Technically scripts are the only users of its dedicated connection meaning no lock is required, but since we may add support for threaded coroutines in the future, their dedicated busy lock will remain.
Acquiring a token
Currently only 1 token can exist per lock. There's 4 ways to acquire a token:
-
TryBeginBusyOperation -
Acquire async
This can be done with one of the three methods:
// cancellationToken cancels the acquire operation, and causes the task to produce null. Task<IBusyToken?> BeginBusyOperation(CancellationToken busyCancellation) // Timeout cancels the acquire operation, as does the cancellationToken, and causes the task to produce null. Task<IBusyToken?> BeginBusyOperation(int timeoutMilliseconds, CancellationToken busyCancellation) // This method is called by both of the above. Task<IBusyToken?> BeginBusyOperation(BusyTokenRequest request) -
Acquire async using activities
This can be done with one of the two methods:
Task<IBusyToken?> BeginBusyOperationUsingActivity(string caption, string message, CancellationToken busyCancellation) // This method is called by the above method Task<IBusyToken?> BeginBusyOperationUsingActivity(BusyTokenRequestUsingActivity request) -
Acquire async from within activity
This can be done with one of the two methods:
Task<IBusyToken?> BeginBusyOperationFromActivity(CancellationToken busyCancellation) // This method is called by the above method Task<IBusyToken?> BeginBusyOperationFromActivity(BusyTokenRequestFromActivity request)
The first Try immediately tries to obtain the token. If no one is using it and there's no quick release waiters (see Quick Release Intention for more info), then it creates a new token and returns it.
The next way is simple and just waits in the background until either it obtains the token or the busyCancellation token becomes cancelled, in which case, the task produces null.
You can also obtain the token by starting a new activity, so that it shows up in the activity status bar. But if you're already running in an activity, you can show from within the activity.
Foreground dialogs
When obtaining the token by using a request struct, you can provide InForegroundInfo to specify you want to show a foreground dialog relative to a top level. The dialog closes once the token is acquired or the activity is cancelled, or if calling from within an activity, the caller cancels the busy operation.
Quick Release Intention
This system is used for actions that have a high priority (such as modify scan result's or saved address' value). To obtain a token with this mode, you must use one of the overloads that accept a request struct, and set QuickReleaseIntention to true.
Then, the event BusyLock.UserQuickReleaseRequested will be invoked.
Ideally the only listener will be the one using the token currently. It should then release the token when the event is fired (it doesn't have to be in the same call frame, it can be eventually).
This is currently only used for the Edit Value commands for scan results and saved addresses; editing the value will pause the scan or memory view auto-refresh until the edit operation completes, then the scan/refresh will continue.