Orion Edwards edited this page Feb 13, 2020 · 7 revisions

public class SerialQueue : IDispatchQueue

This class is the main purpose of the library. It represents a serial queue which will run all it's callbacks sequentially and safely (like a thread) but whose execution actually is performed on the OS threadpool.

SerialQueue(string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by the default TaskThreadPool. This is the default constructor which is intended for normal use

Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

SerialQueue(IThreadPool threadpool, string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by a custom ThreadPool implementation. This primarily exists to enable unit testing, however if you have a custom ThreadPool you could use it here

Parameterthreadpool: The threadpool to queue async actions to
Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

Constructor which allows you to specify a custom threadpool implementation for advanced scenarios. See IThreadPool

string? Name { get; }

Returns the friendly name (if one is set)

SerialQueueFeatures Features { get; }

Returns the enabled features this serial queue has

event EventHandler<UnhandledExceptionEventArgs>? UnhandledException

This event is raised whenever an asynchronous callback function (via DispatchAsync or DispatchAfter) throws an unhandled exception

static SerialQueue? Current

Returns the topmost queue that we are currently executing on, or null if we're not on any queue.
Note this only works for serial queues specifically, it doesn't generalize to any IDispatchQueue

void VerifyQueue()

Checks whether the currently-executing function is on this queue, and throw an OperationInvalidException if it is not

IDisposable DispatchAsync(Action action)

Schedules the given action to run asynchronously on the queue when it is available

Parameteraction: The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet. It is always safe to dispose this token, even if the async action has already run

void DispatchSync(Action action)

Runs the given action on the queue.
Blocks until the action is fully complete.

If the queue is not currently busy processing asynchronous actions (a very common state), this should have the same performance characteristics as a simple lock, so it is often nice and convenient.
The SerialQueue guarantees the action will run on the calling thread (it will NOT thread-jump).
Other implementations of IDispatchQueue reserve the right to run the action on a different thread (e.g WPF Dispatcher)

Parameteraction: The function to run

IDisposable DispatchAfter(TimeSpan dueTime, Action action)

Schedules the given action to run asynchronously on the queue after dueTime.
The function is not guaranteed to run at dueTime as the queue may be busy, it will run when next able.

ParameterdueTime: Delay before running the action
Parameter `action": The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet.
It is always safe to dispose this token, even if the async action has already run

void Dispose()

Shuts down the queue. All unstarted async actions will be dropped, and any future attempts to call one of the Dispatch functions will throw an ObjectDisposedException

public class DispatchQueueSynchronizationContext : SynchronizationContext

Enables capture of a serial queue as a SynchronizationContext for async/await.
You shouldn't need to interact with this class yourself

DispatchQueueSynchronizationContext(IDispatchQueue queue)

Constructs a new DispatchQueueSynchronizationContext wrapping the given queue

Parameterqueue: Queue to post actions to

[Flags] enum SerialQueueFeatures

Use these to turn on and off various features of the serial queue for performance reasons

None: Only basic functionality SynchronizationContext: If enabled, you may use this queue with async/await All: All features enabled

public static class IDispatchQueueExtensions

Useful extension methods for queues

static T DispatchSync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchSync that calls a value-producing function and returns it's result

static Task<T> DispatchAsync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchAsync that calls a value-producing function on the queue and returns it's result via a Task

Note: the task "completes" on the dispatch queue, so if you use ConfigureAwait(false) or TaskContinuationOptions.ExecuteSychronously, you will still be running on the dispatch queue

static DispatchQueueAwaiter GetAwaiter(this IDispatchQueue queue)

This allows you await directly on a queue, which is handy for queue-jumping if you are already in the middle of an async method.

public interface IDispatchQueue : IDisposable

The SerialQueue class implements the IDispatchQueue interface, which provides a basic abstraction over a queue. It is often very useful to create other kinds of queues linked to UI threads, other custom threads, etc so you can present your application code with a uniform interface by using the IDispatchQueue interface rather than a SerialQueue directly. For example, I have often used a DispatcherQueue in WPF apps, which is an implementation of IDispatchQueue that just forwards methods to the WPF Dispatcher (representing the UI thread).

The IDispatchQueue interface requires these methods, which should behave in a similar fashion to the SerialQueue

IDisposableDispatchAsync(Actionaction)voidDispatchSync(Actionaction)IDisposableDispatchAfter(TimeSpandueTime,Actionaction)voidVerifyQueue()

You may chose not to implement some of these methods (e.g. VerifyQueue) if you never need them

IThreadPool Interface

The SerialQueue abstracts the underlying threadpool with this interface. It is intended to support unit testing as well as advanced behaviour. For example, if you do not wish your serial queues to post callbacks to the built in .NET threadpool, and would rather use a custom threadpool, you can achieve this using a custom implementation of IThreadPool

You might want to do this if, for example you post a lot of long-running operations to your serial queues (the default CLR threadpool doesn't deal with lots of long-running operations very well) or if you wish to limit the amount of things that run in parallel (The default CLR threadpool will use all the cores in your machine, you might wish to limit that to say 1 or 2 underlying OS threads)

void QueueWorkItem(Action action)

The serial queue uses this to post an async action to the threadpool

Parameteraction: The function to run. Note this function will be an internal SerialQueue worker function, not a direct user-supplied function

IDisposable Schedule(TimeSpan dueTime, Action action)

The serial queue uses this to create a timer. The timer doesn't have to be related to the the threadpool, it is only in the IThreadPool interface so the queue can have one dependency rather than 2.

ParameterdueTime: When the timer should fire
Parameteraction: The function to run after the timer elapses
Returns A disposable token to cancel the timer

If you are creating your own implementation of IThreadPool and don't need any custom timer behaviour, please just use the default implementation which is:

publicIDisposableSchedule(TimeSpandueTime,Actionaction){returnnewTimer(_ =>action(),null,dueTime,TimeSpan.FromMilliseconds(-1));}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Orion Edwards edited this page Feb 13, 2020 · 7 revisions

public class SerialQueue : IDispatchQueue

This class is the main purpose of the library. It represents a serial queue which will run all it's callbacks sequentially and safely (like a thread) but whose execution actually is performed on the OS threadpool.

SerialQueue(string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by the default TaskThreadPool. This is the default constructor which is intended for normal use

Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

SerialQueue(IThreadPool threadpool, string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by a custom ThreadPool implementation. This primarily exists to enable unit testing, however if you have a custom ThreadPool you could use it here

Parameterthreadpool: The threadpool to queue async actions to
Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

Constructor which allows you to specify a custom threadpool implementation for advanced scenarios. See IThreadPool

string? Name { get; }

Returns the friendly name (if one is set)

SerialQueueFeatures Features { get; }

Returns the enabled features this serial queue has

event EventHandler<UnhandledExceptionEventArgs>? UnhandledException

This event is raised whenever an asynchronous callback function (via DispatchAsync or DispatchAfter) throws an unhandled exception

static SerialQueue? Current

Returns the topmost queue that we are currently executing on, or null if we're not on any queue.
Note this only works for serial queues specifically, it doesn't generalize to any IDispatchQueue

void VerifyQueue()

Checks whether the currently-executing function is on this queue, and throw an OperationInvalidException if it is not

IDisposable DispatchAsync(Action action)

Schedules the given action to run asynchronously on the queue when it is available

Parameteraction: The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet. It is always safe to dispose this token, even if the async action has already run

void DispatchSync(Action action)

Runs the given action on the queue.
Blocks until the action is fully complete.

If the queue is not currently busy processing asynchronous actions (a very common state), this should have the same performance characteristics as a simple lock, so it is often nice and convenient.
The SerialQueue guarantees the action will run on the calling thread (it will NOT thread-jump).
Other implementations of IDispatchQueue reserve the right to run the action on a different thread (e.g WPF Dispatcher)

Parameteraction: The function to run

IDisposable DispatchAfter(TimeSpan dueTime, Action action)

Schedules the given action to run asynchronously on the queue after dueTime.
The function is not guaranteed to run at dueTime as the queue may be busy, it will run when next able.

ParameterdueTime: Delay before running the action
Parameter `action": The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet.
It is always safe to dispose this token, even if the async action has already run

void Dispose()

Shuts down the queue. All unstarted async actions will be dropped, and any future attempts to call one of the Dispatch functions will throw an ObjectDisposedException

public class DispatchQueueSynchronizationContext : SynchronizationContext

Enables capture of a serial queue as a SynchronizationContext for async/await.
You shouldn't need to interact with this class yourself

DispatchQueueSynchronizationContext(IDispatchQueue queue)

Constructs a new DispatchQueueSynchronizationContext wrapping the given queue

Parameterqueue: Queue to post actions to

[Flags] enum SerialQueueFeatures

Use these to turn on and off various features of the serial queue for performance reasons

None: Only basic functionality SynchronizationContext: If enabled, you may use this queue with async/await All: All features enabled

public static class IDispatchQueueExtensions

Useful extension methods for queues

static T DispatchSync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchSync that calls a value-producing function and returns it's result

static Task<T> DispatchAsync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchAsync that calls a value-producing function on the queue and returns it's result via a Task

Note: the task "completes" on the dispatch queue, so if you use ConfigureAwait(false) or TaskContinuationOptions.ExecuteSychronously, you will still be running on the dispatch queue

static DispatchQueueAwaiter GetAwaiter(this IDispatchQueue queue)

This allows you await directly on a queue, which is handy for queue-jumping if you are already in the middle of an async method.

public interface IDispatchQueue : IDisposable

The SerialQueue class implements the IDispatchQueue interface, which provides a basic abstraction over a queue. It is often very useful to create other kinds of queues linked to UI threads, other custom threads, etc so you can present your application code with a uniform interface by using the IDispatchQueue interface rather than a SerialQueue directly. For example, I have often used a DispatcherQueue in WPF apps, which is an implementation of IDispatchQueue that just forwards methods to the WPF Dispatcher (representing the UI thread).

The IDispatchQueue interface requires these methods, which should behave in a similar fashion to the SerialQueue

IDisposableDispatchAsync(Actionaction)voidDispatchSync(Actionaction)IDisposableDispatchAfter(TimeSpandueTime,Actionaction)voidVerifyQueue()

You may chose not to implement some of these methods (e.g. VerifyQueue) if you never need them

IThreadPool Interface

The SerialQueue abstracts the underlying threadpool with this interface. It is intended to support unit testing as well as advanced behaviour. For example, if you do not wish your serial queues to post callbacks to the built in .NET threadpool, and would rather use a custom threadpool, you can achieve this using a custom implementation of IThreadPool

You might want to do this if, for example you post a lot of long-running operations to your serial queues (the default CLR threadpool doesn't deal with lots of long-running operations very well) or if you wish to limit the amount of things that run in parallel (The default CLR threadpool will use all the cores in your machine, you might wish to limit that to say 1 or 2 underlying OS threads)

void QueueWorkItem(Action action)

The serial queue uses this to post an async action to the threadpool

Parameteraction: The function to run. Note this function will be an internal SerialQueue worker function, not a direct user-supplied function

IDisposable Schedule(TimeSpan dueTime, Action action)

The serial queue uses this to create a timer. The timer doesn't have to be related to the the threadpool, it is only in the IThreadPool interface so the queue can have one dependency rather than 2.

ParameterdueTime: When the timer should fire
Parameteraction: The function to run after the timer elapses
Returns A disposable token to cancel the timer

If you are creating your own implementation of IThreadPool and don't need any custom timer behaviour, please just use the default implementation which is:

publicIDisposableSchedule(TimeSpandueTime,Actionaction){returnnewTimer(_ =>action(),null,dueTime,TimeSpan.FromMilliseconds(-1));}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Orion Edwards edited this page Feb 13, 2020 · 7 revisions

public class SerialQueue : IDispatchQueue

This class is the main purpose of the library. It represents a serial queue which will run all it's callbacks sequentially and safely (like a thread) but whose execution actually is performed on the OS threadpool.

SerialQueue(string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by the default TaskThreadPool. This is the default constructor which is intended for normal use

Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

SerialQueue(IThreadPool threadpool, string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by a custom ThreadPool implementation. This primarily exists to enable unit testing, however if you have a custom ThreadPool you could use it here

Parameterthreadpool: The threadpool to queue async actions to
Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

Constructor which allows you to specify a custom threadpool implementation for advanced scenarios. See IThreadPool

string? Name { get; }

Returns the friendly name (if one is set)

SerialQueueFeatures Features { get; }

Returns the enabled features this serial queue has

event EventHandler<UnhandledExceptionEventArgs>? UnhandledException

This event is raised whenever an asynchronous callback function (via DispatchAsync or DispatchAfter) throws an unhandled exception

static SerialQueue? Current

Returns the topmost queue that we are currently executing on, or null if we're not on any queue.
Note this only works for serial queues specifically, it doesn't generalize to any IDispatchQueue

void VerifyQueue()

Checks whether the currently-executing function is on this queue, and throw an OperationInvalidException if it is not

IDisposable DispatchAsync(Action action)

Schedules the given action to run asynchronously on the queue when it is available

Parameteraction: The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet. It is always safe to dispose this token, even if the async action has already run

void DispatchSync(Action action)

Runs the given action on the queue.
Blocks until the action is fully complete.

If the queue is not currently busy processing asynchronous actions (a very common state), this should have the same performance characteristics as a simple lock, so it is often nice and convenient.
The SerialQueue guarantees the action will run on the calling thread (it will NOT thread-jump).
Other implementations of IDispatchQueue reserve the right to run the action on a different thread (e.g WPF Dispatcher)

Parameteraction: The function to run

IDisposable DispatchAfter(TimeSpan dueTime, Action action)

Schedules the given action to run asynchronously on the queue after dueTime.
The function is not guaranteed to run at dueTime as the queue may be busy, it will run when next able.

ParameterdueTime: Delay before running the action
Parameter `action": The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet.
It is always safe to dispose this token, even if the async action has already run

void Dispose()

Shuts down the queue. All unstarted async actions will be dropped, and any future attempts to call one of the Dispatch functions will throw an ObjectDisposedException

public class DispatchQueueSynchronizationContext : SynchronizationContext

Enables capture of a serial queue as a SynchronizationContext for async/await.
You shouldn't need to interact with this class yourself

DispatchQueueSynchronizationContext(IDispatchQueue queue)

Constructs a new DispatchQueueSynchronizationContext wrapping the given queue

Parameterqueue: Queue to post actions to

[Flags] enum SerialQueueFeatures

Use these to turn on and off various features of the serial queue for performance reasons

None: Only basic functionality SynchronizationContext: If enabled, you may use this queue with async/await All: All features enabled

public static class IDispatchQueueExtensions

Useful extension methods for queues

static T DispatchSync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchSync that calls a value-producing function and returns it's result

static Task<T> DispatchAsync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchAsync that calls a value-producing function on the queue and returns it's result via a Task

Note: the task "completes" on the dispatch queue, so if you use ConfigureAwait(false) or TaskContinuationOptions.ExecuteSychronously, you will still be running on the dispatch queue

static DispatchQueueAwaiter GetAwaiter(this IDispatchQueue queue)

This allows you await directly on a queue, which is handy for queue-jumping if you are already in the middle of an async method.

public interface IDispatchQueue : IDisposable

The SerialQueue class implements the IDispatchQueue interface, which provides a basic abstraction over a queue. It is often very useful to create other kinds of queues linked to UI threads, other custom threads, etc so you can present your application code with a uniform interface by using the IDispatchQueue interface rather than a SerialQueue directly. For example, I have often used a DispatcherQueue in WPF apps, which is an implementation of IDispatchQueue that just forwards methods to the WPF Dispatcher (representing the UI thread).

The IDispatchQueue interface requires these methods, which should behave in a similar fashion to the SerialQueue

IDisposableDispatchAsync(Actionaction)voidDispatchSync(Actionaction)IDisposableDispatchAfter(TimeSpandueTime,Actionaction)voidVerifyQueue()

You may chose not to implement some of these methods (e.g. VerifyQueue) if you never need them

IThreadPool Interface

The SerialQueue abstracts the underlying threadpool with this interface. It is intended to support unit testing as well as advanced behaviour. For example, if you do not wish your serial queues to post callbacks to the built in .NET threadpool, and would rather use a custom threadpool, you can achieve this using a custom implementation of IThreadPool

You might want to do this if, for example you post a lot of long-running operations to your serial queues (the default CLR threadpool doesn't deal with lots of long-running operations very well) or if you wish to limit the amount of things that run in parallel (The default CLR threadpool will use all the cores in your machine, you might wish to limit that to say 1 or 2 underlying OS threads)

void QueueWorkItem(Action action)

The serial queue uses this to post an async action to the threadpool

Parameteraction: The function to run. Note this function will be an internal SerialQueue worker function, not a direct user-supplied function

IDisposable Schedule(TimeSpan dueTime, Action action)

The serial queue uses this to create a timer. The timer doesn't have to be related to the the threadpool, it is only in the IThreadPool interface so the queue can have one dependency rather than 2.

ParameterdueTime: When the timer should fire
Parameteraction: The function to run after the timer elapses
Returns A disposable token to cancel the timer

If you are creating your own implementation of IThreadPool and don't need any custom timer behaviour, please just use the default implementation which is:

publicIDisposableSchedule(TimeSpandueTime,Actionaction){returnnewTimer(_ =>action(),null,dueTime,TimeSpan.FromMilliseconds(-1));}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Orion Edwards edited this page Feb 13, 2020 · 7 revisions

public class SerialQueue : IDispatchQueue

This class is the main purpose of the library. It represents a serial queue which will run all it's callbacks sequentially and safely (like a thread) but whose execution actually is performed on the OS threadpool.

SerialQueue(string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by the default TaskThreadPool. This is the default constructor which is intended for normal use

Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

SerialQueue(IThreadPool threadpool, string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by a custom ThreadPool implementation. This primarily exists to enable unit testing, however if you have a custom ThreadPool you could use it here

Parameterthreadpool: The threadpool to queue async actions to
Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

Constructor which allows you to specify a custom threadpool implementation for advanced scenarios. See IThreadPool

string? Name { get; }

Returns the friendly name (if one is set)

SerialQueueFeatures Features { get; }

Returns the enabled features this serial queue has

event EventHandler<UnhandledExceptionEventArgs>? UnhandledException

This event is raised whenever an asynchronous callback function (via DispatchAsync or DispatchAfter) throws an unhandled exception

static SerialQueue? Current

Returns the topmost queue that we are currently executing on, or null if we're not on any queue.
Note this only works for serial queues specifically, it doesn't generalize to any IDispatchQueue

void VerifyQueue()

Checks whether the currently-executing function is on this queue, and throw an OperationInvalidException if it is not

IDisposable DispatchAsync(Action action)

Schedules the given action to run asynchronously on the queue when it is available

Parameteraction: The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet. It is always safe to dispose this token, even if the async action has already run

void DispatchSync(Action action)

Runs the given action on the queue.
Blocks until the action is fully complete.

If the queue is not currently busy processing asynchronous actions (a very common state), this should have the same performance characteristics as a simple lock, so it is often nice and convenient.
The SerialQueue guarantees the action will run on the calling thread (it will NOT thread-jump).
Other implementations of IDispatchQueue reserve the right to run the action on a different thread (e.g WPF Dispatcher)

Parameteraction: The function to run

IDisposable DispatchAfter(TimeSpan dueTime, Action action)

Schedules the given action to run asynchronously on the queue after dueTime.
The function is not guaranteed to run at dueTime as the queue may be busy, it will run when next able.

ParameterdueTime: Delay before running the action
Parameter `action": The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet.
It is always safe to dispose this token, even if the async action has already run

void Dispose()

Shuts down the queue. All unstarted async actions will be dropped, and any future attempts to call one of the Dispatch functions will throw an ObjectDisposedException

public class DispatchQueueSynchronizationContext : SynchronizationContext

Enables capture of a serial queue as a SynchronizationContext for async/await.
You shouldn't need to interact with this class yourself

DispatchQueueSynchronizationContext(IDispatchQueue queue)

Constructs a new DispatchQueueSynchronizationContext wrapping the given queue

Parameterqueue: Queue to post actions to

[Flags] enum SerialQueueFeatures

Use these to turn on and off various features of the serial queue for performance reasons

None: Only basic functionality SynchronizationContext: If enabled, you may use this queue with async/await All: All features enabled

public static class IDispatchQueueExtensions

Useful extension methods for queues

static T DispatchSync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchSync that calls a value-producing function and returns it's result

static Task<T> DispatchAsync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchAsync that calls a value-producing function on the queue and returns it's result via a Task

Note: the task "completes" on the dispatch queue, so if you use ConfigureAwait(false) or TaskContinuationOptions.ExecuteSychronously, you will still be running on the dispatch queue

static DispatchQueueAwaiter GetAwaiter(this IDispatchQueue queue)

This allows you await directly on a queue, which is handy for queue-jumping if you are already in the middle of an async method.

public interface IDispatchQueue : IDisposable

The SerialQueue class implements the IDispatchQueue interface, which provides a basic abstraction over a queue. It is often very useful to create other kinds of queues linked to UI threads, other custom threads, etc so you can present your application code with a uniform interface by using the IDispatchQueue interface rather than a SerialQueue directly. For example, I have often used a DispatcherQueue in WPF apps, which is an implementation of IDispatchQueue that just forwards methods to the WPF Dispatcher (representing the UI thread).

The IDispatchQueue interface requires these methods, which should behave in a similar fashion to the SerialQueue

IDisposableDispatchAsync(Actionaction)voidDispatchSync(Actionaction)IDisposableDispatchAfter(TimeSpandueTime,Actionaction)voidVerifyQueue()

You may chose not to implement some of these methods (e.g. VerifyQueue) if you never need them

IThreadPool Interface

The SerialQueue abstracts the underlying threadpool with this interface. It is intended to support unit testing as well as advanced behaviour. For example, if you do not wish your serial queues to post callbacks to the built in .NET threadpool, and would rather use a custom threadpool, you can achieve this using a custom implementation of IThreadPool

You might want to do this if, for example you post a lot of long-running operations to your serial queues (the default CLR threadpool doesn't deal with lots of long-running operations very well) or if you wish to limit the amount of things that run in parallel (The default CLR threadpool will use all the cores in your machine, you might wish to limit that to say 1 or 2 underlying OS threads)

void QueueWorkItem(Action action)

The serial queue uses this to post an async action to the threadpool

Parameteraction: The function to run. Note this function will be an internal SerialQueue worker function, not a direct user-supplied function

IDisposable Schedule(TimeSpan dueTime, Action action)

The serial queue uses this to create a timer. The timer doesn't have to be related to the the threadpool, it is only in the IThreadPool interface so the queue can have one dependency rather than 2.

ParameterdueTime: When the timer should fire
Parameteraction: The function to run after the timer elapses
Returns A disposable token to cancel the timer

If you are creating your own implementation of IThreadPool and don't need any custom timer behaviour, please just use the default implementation which is:

publicIDisposableSchedule(TimeSpandueTime,Actionaction){returnnewTimer(_ =>action(),null,dueTime,TimeSpan.FromMilliseconds(-1));}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Orion Edwards edited this page Feb 13, 2020 · 7 revisions

public class SerialQueue : IDispatchQueue

This class is the main purpose of the library. It represents a serial queue which will run all it's callbacks sequentially and safely (like a thread) but whose execution actually is performed on the OS threadpool.

SerialQueue(string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by the default TaskThreadPool. This is the default constructor which is intended for normal use

Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

SerialQueue(IThreadPool threadpool, string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by a custom ThreadPool implementation. This primarily exists to enable unit testing, however if you have a custom ThreadPool you could use it here

Parameterthreadpool: The threadpool to queue async actions to
Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

Constructor which allows you to specify a custom threadpool implementation for advanced scenarios. See IThreadPool

string? Name { get; }

Returns the friendly name (if one is set)

SerialQueueFeatures Features { get; }

Returns the enabled features this serial queue has

event EventHandler<UnhandledExceptionEventArgs>? UnhandledException

This event is raised whenever an asynchronous callback function (via DispatchAsync or DispatchAfter) throws an unhandled exception

static SerialQueue? Current

Returns the topmost queue that we are currently executing on, or null if we're not on any queue.
Note this only works for serial queues specifically, it doesn't generalize to any IDispatchQueue

void VerifyQueue()

Checks whether the currently-executing function is on this queue, and throw an OperationInvalidException if it is not

IDisposable DispatchAsync(Action action)

Schedules the given action to run asynchronously on the queue when it is available

Parameteraction: The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet. It is always safe to dispose this token, even if the async action has already run

void DispatchSync(Action action)

Runs the given action on the queue.
Blocks until the action is fully complete.

If the queue is not currently busy processing asynchronous actions (a very common state), this should have the same performance characteristics as a simple lock, so it is often nice and convenient.
The SerialQueue guarantees the action will run on the calling thread (it will NOT thread-jump).
Other implementations of IDispatchQueue reserve the right to run the action on a different thread (e.g WPF Dispatcher)

Parameteraction: The function to run

IDisposable DispatchAfter(TimeSpan dueTime, Action action)

Schedules the given action to run asynchronously on the queue after dueTime.
The function is not guaranteed to run at dueTime as the queue may be busy, it will run when next able.

ParameterdueTime: Delay before running the action
Parameter `action": The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet.
It is always safe to dispose this token, even if the async action has already run

void Dispose()

Shuts down the queue. All unstarted async actions will be dropped, and any future attempts to call one of the Dispatch functions will throw an ObjectDisposedException

public class DispatchQueueSynchronizationContext : SynchronizationContext

Enables capture of a serial queue as a SynchronizationContext for async/await.
You shouldn't need to interact with this class yourself

DispatchQueueSynchronizationContext(IDispatchQueue queue)

Constructs a new DispatchQueueSynchronizationContext wrapping the given queue

Parameterqueue: Queue to post actions to

[Flags] enum SerialQueueFeatures

Use these to turn on and off various features of the serial queue for performance reasons

None: Only basic functionality SynchronizationContext: If enabled, you may use this queue with async/await All: All features enabled

public static class IDispatchQueueExtensions

Useful extension methods for queues

static T DispatchSync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchSync that calls a value-producing function and returns it's result

static Task<T> DispatchAsync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchAsync that calls a value-producing function on the queue and returns it's result via a Task

Note: the task "completes" on the dispatch queue, so if you use ConfigureAwait(false) or TaskContinuationOptions.ExecuteSychronously, you will still be running on the dispatch queue

static DispatchQueueAwaiter GetAwaiter(this IDispatchQueue queue)

This allows you await directly on a queue, which is handy for queue-jumping if you are already in the middle of an async method.

public interface IDispatchQueue : IDisposable

The SerialQueue class implements the IDispatchQueue interface, which provides a basic abstraction over a queue. It is often very useful to create other kinds of queues linked to UI threads, other custom threads, etc so you can present your application code with a uniform interface by using the IDispatchQueue interface rather than a SerialQueue directly. For example, I have often used a DispatcherQueue in WPF apps, which is an implementation of IDispatchQueue that just forwards methods to the WPF Dispatcher (representing the UI thread).

The IDispatchQueue interface requires these methods, which should behave in a similar fashion to the SerialQueue

IDisposableDispatchAsync(Actionaction)voidDispatchSync(Actionaction)IDisposableDispatchAfter(TimeSpandueTime,Actionaction)voidVerifyQueue()

You may chose not to implement some of these methods (e.g. VerifyQueue) if you never need them

IThreadPool Interface

The SerialQueue abstracts the underlying threadpool with this interface. It is intended to support unit testing as well as advanced behaviour. For example, if you do not wish your serial queues to post callbacks to the built in .NET threadpool, and would rather use a custom threadpool, you can achieve this using a custom implementation of IThreadPool

You might want to do this if, for example you post a lot of long-running operations to your serial queues (the default CLR threadpool doesn't deal with lots of long-running operations very well) or if you wish to limit the amount of things that run in parallel (The default CLR threadpool will use all the cores in your machine, you might wish to limit that to say 1 or 2 underlying OS threads)

void QueueWorkItem(Action action)

The serial queue uses this to post an async action to the threadpool

Parameteraction: The function to run. Note this function will be an internal SerialQueue worker function, not a direct user-supplied function

IDisposable Schedule(TimeSpan dueTime, Action action)

The serial queue uses this to create a timer. The timer doesn't have to be related to the the threadpool, it is only in the IThreadPool interface so the queue can have one dependency rather than 2.

ParameterdueTime: When the timer should fire
Parameteraction: The function to run after the timer elapses
Returns A disposable token to cancel the timer

If you are creating your own implementation of IThreadPool and don't need any custom timer behaviour, please just use the default implementation which is:

publicIDisposableSchedule(TimeSpandueTime,Actionaction){returnnewTimer(_ =>action(),null,dueTime,TimeSpan.FromMilliseconds(-1));}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Orion Edwards edited this page Feb 13, 2020 · 7 revisions

public class SerialQueue : IDispatchQueue

This class is the main purpose of the library. It represents a serial queue which will run all it's callbacks sequentially and safely (like a thread) but whose execution actually is performed on the OS threadpool.

SerialQueue(string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by the default TaskThreadPool. This is the default constructor which is intended for normal use

Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

SerialQueue(IThreadPool threadpool, string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by a custom ThreadPool implementation. This primarily exists to enable unit testing, however if you have a custom ThreadPool you could use it here

Parameterthreadpool: The threadpool to queue async actions to
Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

Constructor which allows you to specify a custom threadpool implementation for advanced scenarios. See IThreadPool

string? Name { get; }

Returns the friendly name (if one is set)

SerialQueueFeatures Features { get; }

Returns the enabled features this serial queue has

event EventHandler<UnhandledExceptionEventArgs>? UnhandledException

This event is raised whenever an asynchronous callback function (via DispatchAsync or DispatchAfter) throws an unhandled exception

static SerialQueue? Current

Returns the topmost queue that we are currently executing on, or null if we're not on any queue.
Note this only works for serial queues specifically, it doesn't generalize to any IDispatchQueue

void VerifyQueue()

Checks whether the currently-executing function is on this queue, and throw an OperationInvalidException if it is not

IDisposable DispatchAsync(Action action)

Schedules the given action to run asynchronously on the queue when it is available

Parameteraction: The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet. It is always safe to dispose this token, even if the async action has already run

void DispatchSync(Action action)

Runs the given action on the queue.
Blocks until the action is fully complete.

If the queue is not currently busy processing asynchronous actions (a very common state), this should have the same performance characteristics as a simple lock, so it is often nice and convenient.
The SerialQueue guarantees the action will run on the calling thread (it will NOT thread-jump).
Other implementations of IDispatchQueue reserve the right to run the action on a different thread (e.g WPF Dispatcher)

Parameteraction: The function to run

IDisposable DispatchAfter(TimeSpan dueTime, Action action)

Schedules the given action to run asynchronously on the queue after dueTime.
The function is not guaranteed to run at dueTime as the queue may be busy, it will run when next able.

ParameterdueTime: Delay before running the action
Parameter `action": The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet.
It is always safe to dispose this token, even if the async action has already run

void Dispose()

Shuts down the queue. All unstarted async actions will be dropped, and any future attempts to call one of the Dispatch functions will throw an ObjectDisposedException

public class DispatchQueueSynchronizationContext : SynchronizationContext

Enables capture of a serial queue as a SynchronizationContext for async/await.
You shouldn't need to interact with this class yourself

DispatchQueueSynchronizationContext(IDispatchQueue queue)

Constructs a new DispatchQueueSynchronizationContext wrapping the given queue

Parameterqueue: Queue to post actions to

[Flags] enum SerialQueueFeatures

Use these to turn on and off various features of the serial queue for performance reasons

None: Only basic functionality SynchronizationContext: If enabled, you may use this queue with async/await All: All features enabled

public static class IDispatchQueueExtensions

Useful extension methods for queues

static T DispatchSync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchSync that calls a value-producing function and returns it's result

static Task<T> DispatchAsync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchAsync that calls a value-producing function on the queue and returns it's result via a Task

Note: the task "completes" on the dispatch queue, so if you use ConfigureAwait(false) or TaskContinuationOptions.ExecuteSychronously, you will still be running on the dispatch queue

static DispatchQueueAwaiter GetAwaiter(this IDispatchQueue queue)

This allows you await directly on a queue, which is handy for queue-jumping if you are already in the middle of an async method.

public interface IDispatchQueue : IDisposable

The SerialQueue class implements the IDispatchQueue interface, which provides a basic abstraction over a queue. It is often very useful to create other kinds of queues linked to UI threads, other custom threads, etc so you can present your application code with a uniform interface by using the IDispatchQueue interface rather than a SerialQueue directly. For example, I have often used a DispatcherQueue in WPF apps, which is an implementation of IDispatchQueue that just forwards methods to the WPF Dispatcher (representing the UI thread).

The IDispatchQueue interface requires these methods, which should behave in a similar fashion to the SerialQueue

IDisposableDispatchAsync(Actionaction)voidDispatchSync(Actionaction)IDisposableDispatchAfter(TimeSpandueTime,Actionaction)voidVerifyQueue()

You may chose not to implement some of these methods (e.g. VerifyQueue) if you never need them

IThreadPool Interface

The SerialQueue abstracts the underlying threadpool with this interface. It is intended to support unit testing as well as advanced behaviour. For example, if you do not wish your serial queues to post callbacks to the built in .NET threadpool, and would rather use a custom threadpool, you can achieve this using a custom implementation of IThreadPool

You might want to do this if, for example you post a lot of long-running operations to your serial queues (the default CLR threadpool doesn't deal with lots of long-running operations very well) or if you wish to limit the amount of things that run in parallel (The default CLR threadpool will use all the cores in your machine, you might wish to limit that to say 1 or 2 underlying OS threads)

void QueueWorkItem(Action action)

The serial queue uses this to post an async action to the threadpool

Parameteraction: The function to run. Note this function will be an internal SerialQueue worker function, not a direct user-supplied function

IDisposable Schedule(TimeSpan dueTime, Action action)

The serial queue uses this to create a timer. The timer doesn't have to be related to the the threadpool, it is only in the IThreadPool interface so the queue can have one dependency rather than 2.

ParameterdueTime: When the timer should fire
Parameteraction: The function to run after the timer elapses
Returns A disposable token to cancel the timer

If you are creating your own implementation of IThreadPool and don't need any custom timer behaviour, please just use the default implementation which is:

publicIDisposableSchedule(TimeSpandueTime,Actionaction){returnnewTimer(_ =>action(),null,dueTime,TimeSpan.FromMilliseconds(-1));}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Orion Edwards edited this page Feb 13, 2020 · 7 revisions

public class SerialQueue : IDispatchQueue

This class is the main purpose of the library. It represents a serial queue which will run all it's callbacks sequentially and safely (like a thread) but whose execution actually is performed on the OS threadpool.

SerialQueue(string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by the default TaskThreadPool. This is the default constructor which is intended for normal use

Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

SerialQueue(IThreadPool threadpool, string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by a custom ThreadPool implementation. This primarily exists to enable unit testing, however if you have a custom ThreadPool you could use it here

Parameterthreadpool: The threadpool to queue async actions to
Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

Constructor which allows you to specify a custom threadpool implementation for advanced scenarios. See IThreadPool

string? Name { get; }

Returns the friendly name (if one is set)

SerialQueueFeatures Features { get; }

Returns the enabled features this serial queue has

event EventHandler<UnhandledExceptionEventArgs>? UnhandledException

This event is raised whenever an asynchronous callback function (via DispatchAsync or DispatchAfter) throws an unhandled exception

static SerialQueue? Current

Returns the topmost queue that we are currently executing on, or null if we're not on any queue.
Note this only works for serial queues specifically, it doesn't generalize to any IDispatchQueue

void VerifyQueue()

Checks whether the currently-executing function is on this queue, and throw an OperationInvalidException if it is not

IDisposable DispatchAsync(Action action)

Schedules the given action to run asynchronously on the queue when it is available

Parameteraction: The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet. It is always safe to dispose this token, even if the async action has already run

void DispatchSync(Action action)

Runs the given action on the queue.
Blocks until the action is fully complete.

If the queue is not currently busy processing asynchronous actions (a very common state), this should have the same performance characteristics as a simple lock, so it is often nice and convenient.
The SerialQueue guarantees the action will run on the calling thread (it will NOT thread-jump).
Other implementations of IDispatchQueue reserve the right to run the action on a different thread (e.g WPF Dispatcher)

Parameteraction: The function to run

IDisposable DispatchAfter(TimeSpan dueTime, Action action)

Schedules the given action to run asynchronously on the queue after dueTime.
The function is not guaranteed to run at dueTime as the queue may be busy, it will run when next able.

ParameterdueTime: Delay before running the action
Parameter `action": The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet.
It is always safe to dispose this token, even if the async action has already run

void Dispose()

Shuts down the queue. All unstarted async actions will be dropped, and any future attempts to call one of the Dispatch functions will throw an ObjectDisposedException

public class DispatchQueueSynchronizationContext : SynchronizationContext

Enables capture of a serial queue as a SynchronizationContext for async/await.
You shouldn't need to interact with this class yourself

DispatchQueueSynchronizationContext(IDispatchQueue queue)

Constructs a new DispatchQueueSynchronizationContext wrapping the given queue

Parameterqueue: Queue to post actions to

[Flags] enum SerialQueueFeatures

Use these to turn on and off various features of the serial queue for performance reasons

None: Only basic functionality SynchronizationContext: If enabled, you may use this queue with async/await All: All features enabled

public static class IDispatchQueueExtensions

Useful extension methods for queues

static T DispatchSync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchSync that calls a value-producing function and returns it's result

static Task<T> DispatchAsync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchAsync that calls a value-producing function on the queue and returns it's result via a Task

Note: the task "completes" on the dispatch queue, so if you use ConfigureAwait(false) or TaskContinuationOptions.ExecuteSychronously, you will still be running on the dispatch queue

static DispatchQueueAwaiter GetAwaiter(this IDispatchQueue queue)

This allows you await directly on a queue, which is handy for queue-jumping if you are already in the middle of an async method.

public interface IDispatchQueue : IDisposable

The SerialQueue class implements the IDispatchQueue interface, which provides a basic abstraction over a queue. It is often very useful to create other kinds of queues linked to UI threads, other custom threads, etc so you can present your application code with a uniform interface by using the IDispatchQueue interface rather than a SerialQueue directly. For example, I have often used a DispatcherQueue in WPF apps, which is an implementation of IDispatchQueue that just forwards methods to the WPF Dispatcher (representing the UI thread).

The IDispatchQueue interface requires these methods, which should behave in a similar fashion to the SerialQueue

IDisposableDispatchAsync(Actionaction)voidDispatchSync(Actionaction)IDisposableDispatchAfter(TimeSpandueTime,Actionaction)voidVerifyQueue()

You may chose not to implement some of these methods (e.g. VerifyQueue) if you never need them

IThreadPool Interface

The SerialQueue abstracts the underlying threadpool with this interface. It is intended to support unit testing as well as advanced behaviour. For example, if you do not wish your serial queues to post callbacks to the built in .NET threadpool, and would rather use a custom threadpool, you can achieve this using a custom implementation of IThreadPool

You might want to do this if, for example you post a lot of long-running operations to your serial queues (the default CLR threadpool doesn't deal with lots of long-running operations very well) or if you wish to limit the amount of things that run in parallel (The default CLR threadpool will use all the cores in your machine, you might wish to limit that to say 1 or 2 underlying OS threads)

void QueueWorkItem(Action action)

The serial queue uses this to post an async action to the threadpool

Parameteraction: The function to run. Note this function will be an internal SerialQueue worker function, not a direct user-supplied function

IDisposable Schedule(TimeSpan dueTime, Action action)

The serial queue uses this to create a timer. The timer doesn't have to be related to the the threadpool, it is only in the IThreadPool interface so the queue can have one dependency rather than 2.

ParameterdueTime: When the timer should fire
Parameteraction: The function to run after the timer elapses
Returns A disposable token to cancel the timer

If you are creating your own implementation of IThreadPool and don't need any custom timer behaviour, please just use the default implementation which is:

publicIDisposableSchedule(TimeSpandueTime,Actionaction){returnnewTimer(_ =>action(),null,dueTime,TimeSpan.FromMilliseconds(-1));}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Orion Edwards edited this page Feb 13, 2020 · 7 revisions

public class SerialQueue : IDispatchQueue

This class is the main purpose of the library. It represents a serial queue which will run all it's callbacks sequentially and safely (like a thread) but whose execution actually is performed on the OS threadpool.

SerialQueue(string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by the default TaskThreadPool. This is the default constructor which is intended for normal use

Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

SerialQueue(IThreadPool threadpool, string? name = null, SerialQueueFeatures features = SerialQueueFeatures.All)

Constructs a new SerialQueue backed by a custom ThreadPool implementation. This primarily exists to enable unit testing, however if you have a custom ThreadPool you could use it here

Parameterthreadpool: The threadpool to queue async actions to
Parametername: An optional friendly name for this queue
Parameterfeatures: You may opt-out of certain features in order to reduce overhead. You shouldn't need to do this except in extreme situations as shown by profiling

Constructor which allows you to specify a custom threadpool implementation for advanced scenarios. See IThreadPool

string? Name { get; }

Returns the friendly name (if one is set)

SerialQueueFeatures Features { get; }

Returns the enabled features this serial queue has

event EventHandler<UnhandledExceptionEventArgs>? UnhandledException

This event is raised whenever an asynchronous callback function (via DispatchAsync or DispatchAfter) throws an unhandled exception

static SerialQueue? Current

Returns the topmost queue that we are currently executing on, or null if we're not on any queue.
Note this only works for serial queues specifically, it doesn't generalize to any IDispatchQueue

void VerifyQueue()

Checks whether the currently-executing function is on this queue, and throw an OperationInvalidException if it is not

IDisposable DispatchAsync(Action action)

Schedules the given action to run asynchronously on the queue when it is available

Parameteraction: The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet. It is always safe to dispose this token, even if the async action has already run

void DispatchSync(Action action)

Runs the given action on the queue.
Blocks until the action is fully complete.

If the queue is not currently busy processing asynchronous actions (a very common state), this should have the same performance characteristics as a simple lock, so it is often nice and convenient.
The SerialQueue guarantees the action will run on the calling thread (it will NOT thread-jump).
Other implementations of IDispatchQueue reserve the right to run the action on a different thread (e.g WPF Dispatcher)

Parameteraction: The function to run

IDisposable DispatchAfter(TimeSpan dueTime, Action action)

Schedules the given action to run asynchronously on the queue after dueTime.
The function is not guaranteed to run at dueTime as the queue may be busy, it will run when next able.

ParameterdueTime: Delay before running the action
Parameter `action": The function to run
Returns A disposable token which you can use to cancel the async action if it has not run yet.
It is always safe to dispose this token, even if the async action has already run

void Dispose()

Shuts down the queue. All unstarted async actions will be dropped, and any future attempts to call one of the Dispatch functions will throw an ObjectDisposedException

public class DispatchQueueSynchronizationContext : SynchronizationContext

Enables capture of a serial queue as a SynchronizationContext for async/await.
You shouldn't need to interact with this class yourself

DispatchQueueSynchronizationContext(IDispatchQueue queue)

Constructs a new DispatchQueueSynchronizationContext wrapping the given queue

Parameterqueue: Queue to post actions to

[Flags] enum SerialQueueFeatures

Use these to turn on and off various features of the serial queue for performance reasons

None: Only basic functionality SynchronizationContext: If enabled, you may use this queue with async/await All: All features enabled

public static class IDispatchQueueExtensions

Useful extension methods for queues

static T DispatchSync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchSync that calls a value-producing function and returns it's result

static Task<T> DispatchAsync<T>(this IDispatchQueue queue, Func<T> func)

A wrapper over DispatchAsync that calls a value-producing function on the queue and returns it's result via a Task

Note: the task "completes" on the dispatch queue, so if you use ConfigureAwait(false) or TaskContinuationOptions.ExecuteSychronously, you will still be running on the dispatch queue

static DispatchQueueAwaiter GetAwaiter(this IDispatchQueue queue)

This allows you await directly on a queue, which is handy for queue-jumping if you are already in the middle of an async method.

public interface IDispatchQueue : IDisposable

The SerialQueue class implements the IDispatchQueue interface, which provides a basic abstraction over a queue. It is often very useful to create other kinds of queues linked to UI threads, other custom threads, etc so you can present your application code with a uniform interface by using the IDispatchQueue interface rather than a SerialQueue directly. For example, I have often used a DispatcherQueue in WPF apps, which is an implementation of IDispatchQueue that just forwards methods to the WPF Dispatcher (representing the UI thread).

The IDispatchQueue interface requires these methods, which should behave in a similar fashion to the SerialQueue

IDisposableDispatchAsync(Actionaction)voidDispatchSync(Actionaction)IDisposableDispatchAfter(TimeSpandueTime,Actionaction)voidVerifyQueue()

You may chose not to implement some of these methods (e.g. VerifyQueue) if you never need them

IThreadPool Interface

The SerialQueue abstracts the underlying threadpool with this interface. It is intended to support unit testing as well as advanced behaviour. For example, if you do not wish your serial queues to post callbacks to the built in .NET threadpool, and would rather use a custom threadpool, you can achieve this using a custom implementation of IThreadPool

You might want to do this if, for example you post a lot of long-running operations to your serial queues (the default CLR threadpool doesn't deal with lots of long-running operations very well) or if you wish to limit the amount of things that run in parallel (The default CLR threadpool will use all the cores in your machine, you might wish to limit that to say 1 or 2 underlying OS threads)

void QueueWorkItem(Action action)

The serial queue uses this to post an async action to the threadpool

Parameteraction: The function to run. Note this function will be an internal SerialQueue worker function, not a direct user-supplied function

IDisposable Schedule(TimeSpan dueTime, Action action)

The serial queue uses this to create a timer. The timer doesn't have to be related to the the threadpool, it is only in the IThreadPool interface so the queue can have one dependency rather than 2.

ParameterdueTime: When the timer should fire
Parameteraction: The function to run after the timer elapses
Returns A disposable token to cancel the timer

If you are creating your own implementation of IThreadPool and don't need any custom timer behaviour, please just use the default implementation which is:

publicIDisposableSchedule(TimeSpandueTime,Actionaction){returnnewTimer(_ =>action(),null,dueTime,TimeSpan.FromMilliseconds(-1));}