Batching futures in Dart - flutter

I want to batch many futures into a single request that triggers either when a maximum batch size is reached, or a maximum time since the earliest future was received is reached.
Motivation
In flutter, I have many UI elements which need to display the result of a future, dependent on the data in the UI element.
For instance, I have a widget for a place, and a sub-widget which displays how long it will take to walk to a place. To compute the how long it will take to walk, I issue a request to Google Maps API to get the travel time to the place.
It is more efficient and cost-effective to batch all these API requests into a batch API request. So if there are 100 requests made instantaneously by the widgets, then the futures could be proxied through a single provider, which batches the futures into a single request to Google, and unpacks the result from Google into all the individual requests.
The provider needs to know when to stop waiting for more futures and when to actually issue the request, which should be controllable by the maximum "batch" size (i.e., # of travel time requests), or the maximum amount of time you are willing to wait for batching to take place.
The desired API would be something like:
// Client gives this to tell provider how to compute batch result.
abstract class BatchComputer<K,V> {
Future<List<V>> compute(List<K> batchedInputs);
}
// Batching library returns an object with this interface
// so that client can submit inputs to completed by the Batch provider.
abstract class BatchingFutureProvider<K,V> {
Future<V> submit(K inputValue);
}
// How do you implement this in dart???
BatchingFutureProvider<K,V> create<K,V>(
BatchComputer<K,V> computer,
int maxBatchSize,
Duration maxWaitDuration,
);
Does Dart (or a pub package) already provide this batching functionality, and if not, how would you implement the create function above?

This sounds perfectly reasonable, but also very specialized.
You need a way to represent a query, to combine these queries into a single super-query, and to split the super-result into individual results afterwards, which is what your BatchComputer does. Then you need a queue which you can flush through that under some conditions.
One thing that is clear is that you will need to use Completers for the results because you always need that when you want to return a future before you have the value or future to complete it with.
The approach I would choose would be:
import "dart:async";
/// A batch of requests to be handled together.
///
/// Collects [Request]s until the pending requests are flushed.
/// Requests can be flushed by calling [flush] or by configuring
/// the batch to automatically flush when reaching certain
/// tresholds.
class BatchRequest<Request, Response> {
final int _maxRequests;
final Duration _maxDelay;
final Future<List<Response>> Function(List<Request>) _compute;
Timer _timeout;
List<Request> _pendingRequests;
List<Completer<Response>> _responseCompleters;
/// Creates a batcher of [Request]s.
///
/// Batches requests until calling [flush]. At that pont, the
/// [batchCompute] function gets the list of pending requests,
/// and it should respond with a list of [Response]s.
/// The response to the a request in the argument list
/// should be at the same index in the response list,
/// and as such, the response list must have the same number
/// of responses as there were requests.
///
/// If [maxRequestsPerBatch] is supplied, requests are automatically
/// flushed whenever there are that many requests pending.
///
/// If [maxDelay] is supplied, requests are automatically flushed
/// when the oldest request has been pending for that long.
/// As such, The [maxDelay] is not the maximal time before a request
/// is answered, just how long sending the request may be delayed.
BatchRequest(Future<List<Response>> Function(List<Request>) batchCompute,
{int maxRequestsPerBatch, Duration maxDelay})
: _compute = batchCompute,
_maxRequests = maxRequestsPerBatch,
_maxDelay = maxDelay;
/// Add a request to the batch.
///
/// The request is stored until the requests are flushed,
/// then the returned future is completed with the result (or error)
/// received from handling the requests.
Future<Response> addRequest(Request request) {
var completer = Completer<Response>();
(_pendingRequests ??= []).add(request);
(_responseCompleters ??= []).add(completer);
if (_pendingRequests.length == _maxRequests) {
_flush();
} else if (_timeout == null && _maxDelay != null) {
_timeout = Timer(_maxDelay, _flush);
}
return completer.future;
}
/// Flush any pending requests immediately.
void flush() {
_flush();
}
void _flush() {
if (_pendingRequests == null) {
assert(_timeout == null);
assert(_responseCompleters == null);
return;
}
if (_timeout != null) {
_timeout.cancel();
_timeout = null;
}
var requests = _pendingRequests;
var completers = _responseCompleters;
_pendingRequests = null;
_responseCompleters = null;
_compute(requests).then((List<Response> results) {
if (results.length != completers.length) {
throw StateError("Wrong number of results. "
"Expected ${completers.length}, got ${results.length}");
}
for (int i = 0; i < results.length; i++) {
completers[i].complete(results[i]);
}
}).catchError((error, stack) {
for (var completer in completers) {
completer.completeError(error, stack);
}
});
}
}
You can use that as, for example:
void main() async {
var b = BatchRequest<int, int>(_compute,
maxRequestsPerBatch: 5, maxDelay: Duration(seconds: 1));
var sw = Stopwatch()..start();
for (int i = 0; i < 8; i++) {
b.addRequest(i).then((r) {
print("${sw.elapsedMilliseconds.toString().padLeft(4)}: $i -> $r");
});
}
}
Future<List<int>> _compute(List<int> args) =>
Future.value([for (var x in args) x + 1]);

See https://pub.dev/packages/batching_future/versions/0.0.2
I have almost exactly the same answer as #lrn, but have put some effort to make the main-line synchronous, and added some documentation.
/// Exposes [createBatcher] which batches computation requests until either
/// a max batch size or max wait duration is reached.
///
import 'dart:async';
import 'dart:collection';
import 'package:quiver/iterables.dart';
import 'package:synchronized/synchronized.dart';
/// Converts input type [K] to output type [V] for every item in
/// [batchedInputs]. There must be exactly one item in output list for every
/// item in input list, and assumes that input[i] => output[i].
abstract class BatchComputer<K, V> {
const BatchComputer();
Future<List<V>> compute(List<K> batchedInputs);
}
/// Interface to submit (possible) batched computation requests.
abstract class BatchingFutureProvider<K, V> {
Future<V> submit(K inputValue);
}
/// Returns a batcher which computes transformations in batch using [computer].
/// The batcher will wait to compute until [maxWaitDuration] is reached since
/// the first item in the current batch is received, or [maxBatchSize] items
/// are in the current batch, whatever happens first.
/// If [maxBatchSize] or [maxWaitDuration] is null, then the triggering
/// condition is ignored, but at least one condition must be supplied.
///
/// Warning: If [maxWaitDuration] is not supplied, then it is possible that
/// a partial batch will never finish computing.
BatchingFutureProvider<K, V> createBatcher<K, V>(BatchComputer<K, V> computer,
{int maxBatchSize, Duration maxWaitDuration}) {
if (!((maxBatchSize != null || maxWaitDuration != null) &&
(maxWaitDuration == null || maxWaitDuration.inMilliseconds > 0) &&
(maxBatchSize == null || maxBatchSize > 0))) {
throw ArgumentError(
"At least one of {maxBatchSize, maxWaitDuration} must be specified and be positive values");
}
return _Impl(computer, maxBatchSize, maxWaitDuration);
}
// Holds the input value and the future to complete it.
class _Payload<K, V> {
final K k;
final Completer<V> completer;
_Payload(this.k, this.completer);
}
enum _ExecuteCommand { EXECUTE }
/// Implements [createBatcher].
class _Impl<K, V> implements BatchingFutureProvider<K, V> {
/// Queues computation requests.
final controller = StreamController<dynamic>();
/// Queues the input values with their futures to complete.
final queue = Queue<_Payload>();
/// Locks access to [listen] to make queue-processing single-threaded.
final lock = Lock();
/// [maxWaitDuration] timer, as a stored reference to cancel early if needed.
Timer timer;
/// Performs the input->output batch transformation.
final BatchComputer computer;
/// See [createBatcher].
final int maxBatchSize;
/// See [createBatcher].
final Duration maxWaitDuration;
_Impl(this.computer, this.maxBatchSize, this.maxWaitDuration) {
controller.stream.listen(listen);
}
void dispose() {
controller.close();
}
#override
Future<V> submit(K inputValue) {
final completer = Completer<V>();
controller.add(_Payload(inputValue, completer));
return completer.future;
}
// Synchronous event-processing logic.
void listen(dynamic event) async {
await lock.synchronized(() {
if (event.runtimeType == _ExecuteCommand) {
if (timer?.isActive ?? true) {
// The timer got reset, so ignore this old request.
// The current timer needs to inactive and non-null
// for the execution to be legitimate.
return;
}
execute();
} else {
addPayload(event as _Payload);
}
return;
});
}
void addPayload(_Payload _payload) {
if (queue.isEmpty && maxWaitDuration != null) {
// This is the first item of the batch.
// Trigger the timer so we are guaranteed to start computing
// this batch before [maxWaitDuration].
timer = Timer(maxWaitDuration, triggerTimer);
}
queue.add(_payload);
if (maxBatchSize != null && queue.length >= maxBatchSize) {
execute();
return;
}
}
void execute() async {
timer?.cancel();
if (queue.isEmpty) {
return;
}
final results = await computer.compute(List<K>.of(queue.map((p) => p.k)));
for (var pair in zip<Object>([queue, results])) {
(pair[0] as _Payload).completer.complete(pair[1] as V);
}
queue.clear();
}
void triggerTimer() {
listen(_ExecuteCommand.EXECUTE);
}
}

Related

Dart Analysis Detects and Unused Field...what am I missing?

As you can see from my code sample, I'm using this variable. I also reference multiple times later in the class.
Flutter Warning - info: The value of the field '_loadTimer' isn't used. (unused_field at [app] lib/models/knowledge_level/pb_cycle_permissions_collection.dart:12)
ng is: info: The value of the field '_loadTimer' isn't used. (unused_field at [app] lib/models/knowledge_level/pb_cycle_permissions_collection.dart:12)
import 'dart:async';
import 'dart:collection';
import 'package:app/data/graphql/queries.dart';
import 'package:app/helpers/shared_logger.dart';
import 'package:flutter/cupertino.dart';
import '../command_permission.dart';
class PBCyclePermissionsCollection
with ListMixin<CommandPermission>, ChangeNotifier {
Timer? _loadTimer;
///
/// CONSTRUCTION AND INITIALIZATION
///
static final PBCyclePermissionsCollection _instance =
PBCyclePermissionsCollection._internal();
factory PBCyclePermissionsCollection() {
return _instance;
}
/// ACCESS SINGLETON VIA myPBCyclePermInstance = PBCyclePermissionsCollection()
PBCyclePermissionsCollection._internal() {
_loadTimer = Timer(_waitFirstLoad, _attemptLoad);
}
///
/// PRIVATE VARIABLES AND METHODS
///
static final Duration _waitFirstLoad = Duration(milliseconds: 500);
static final Duration _waitRetryLoad = Duration(seconds: 2);
static final int _maxAttempts = 4;
int _loadAttempts = 0;
bool _isReady = false;
bool _hasFailed = false;
/// Storage of CommandPermissions List once loaded
final List<CommandPermission> _list = [];
void _attemptLoad() async {
_loadAttempts++;
SharedLogger.I().d('_attemptLoad() current load attempt: ${_loadAttempts}');
try {
final results = await Queries.getCommandPermissions();
var data = results.data!['commandPermissions'];
var permissions = <CommandPermission>[];
for (var item in data) {
permissions.add(CommandPermission.fromJson(item));
}
/// Populated class with loaded objects.
_list.clear();
_list.addAll(permissions);
_isReady = true;
notifyListeners();
} catch (e) {
SharedLogger.I().e('Error loading PBCycle Permissions - ${e}');
_newAttempt();
}
}
void _newAttempt() {
SharedLogger.I().d(
'_newTry() _loadAttempts: ${_loadAttempts} _maxAttempts:${_maxAttempts} '
'creating new loadTimer for another try? : ${!(_loadAttempts >= _maxAttempts)}');
if (_loadAttempts >= _maxAttempts) {
_hasFailed = true;
notifyListeners();
// TODO: do we invalidate any existing data that may have been loaded before? Like if this load cycle is a refresh?
// If so, we should reset _isReady and _list;
return;
}
_loadTimer = Timer(_waitRetryLoad, _attemptLoad);
}
///
/// PUBLIC METHODS
///
bool get isLoaded {
return _isReady;
}
bool get hasFailed {
return _hasFailed;
}
#override
set length(int newLength) {
throw ('length cannot be changed externally');
}
#override
int get length {
return _list.length;
}
#override
CommandPermission operator [](int index) {
return _list[index];
}
#override
void operator []=(int index, CommandPermission value) {
throw ('Cannot modify list from outside');
}
}
Image of IDE with Code Sample and associated Dart Analysis Hints
You aren't actually using it, you're just setting the value multiple times
The answer from Andrew is correct, but a bit unclear since unsure what 'it' refers to. Here's another way to explain what the warning message means:
Notice that the message says you are not using the value. You are using the variable, but not its value. You are assigning the value. To read the value would be using it.
That said, the question is answered, but I think the question is somewhat vague by asking "what am i missing". What do you (OP) want to achieve? I assume it's to not see that warning anymore. And that is what brings me to this post. I have similar issue. I too have a class variable for a Timer and I get this same warning message. One does not need to read the value in order to use a timer but the analyzer doesn't know that. While writing this response I have discovered that you can a suppress warning. How about this:
// ignore: unused_field
Timer? _loadTimer;

Why does FocusOnKeyCallback return bool?

This is the dartdoc & declaration of FocusOnKeyCallback:
/// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey]
/// to receive key events.
///
/// The [node] is the node that received the event.
typedef FocusOnKeyCallback = bool Function(FocusNode node, RawKeyEvent event);
The callback has a return type of bool, but how that value will be used is not clear. Would void have worked here?
(originally asked on Flutter's GitHub: https://github.com/flutter/flutter/issues/45367)
It returns bool to mark that the event was handled by any of focus nodes, and if it wasn't, it calls the assert. Take a look at _handleRawKeyEvent method of FocusManager class:
...
bool handled = false;
for (FocusNode node in <FocusNode>[_primaryFocus, ..._primaryFocus.ancestors]) {
if (node.onKey != null && node.onKey(node, event)) {
assert(_focusDebug('Node $node handled key event $event.'));
handled = true;
break;
}
}
if (!handled) {
assert(_focusDebug('Key event not handled by anyone: $event.'));
}
...
So basically it's to prevent onKey event propagation.

System.TimeoutException in Service Fabric stateful service. Gets struck in ITransaction.CommitAsync method

I have a stateful service that uses IReliableConcurrentQueue to store state. I am using a Stateless ASP.NET Core service to expose two REST endpoints.
The GET endpoint gets specified number of items from the named IReliableConcurrentQueue. It also writes to a separate reliable concurrent queue in the same transaction before returning.
The POST endpoint receives a list of items and adds them to IReliableConcurrentQueue.
When I run the service from Visual Studio locally in debug mode, when I hit one endpoint (GET or POST) repeatedly, it works fine without any issues.
When I hit both endpoints simultaneously, after 3 or 4 hits (sometimes 10), the service stops responding and I see Timeout exceptions.
REST Endpoints (Stateless Service)
[HttpGet]
[Route("{number}/{queueName}")]
public async Task<IEnumerable<QueueItem>> Get(int number, string queueName)
{
IEnumerable<QueueItem> items = await statefulServiceProxy.GetItems(number, queueName);
return items;
}
[HttpPost]
[Route("item")]
public async Task Set([FromBody]IEnumerable<Item> items)
{
await statefulServiceProxy.SetItem(items);
}
Stateful Service
Always gets stuck in CommitAsync function in either ReadAndLockItems() or SetItem().
public async Task<IEnumerable<QueueItem>> GetItems(int number, string queueName)
{
var items = await this.ReadAndLockItems(number, queueName);
return items;
}
private async Task<IEnumerable<QueueItem>> ReadAndLockItems(int number, string queueName)
{
var itemQueue = await this.StateManager.GetOrAddAsync<IReliableConcurrentQueue<QueueItem>>(queueName);
var lockQueue = await this.StateManager.GetOrAddAsync<IReliableConcurrentQueue<QueueItem>>("LockQueue");
List<QueueItem> results = new List<QueueItem>();
using (var tx = this.StateManager.CreateTransaction())
{
for (int i = 0; i < number; i++)
{
var element = await itemQueue.TryDequeueAsync(tx);
if (element.HasValue)
{
results.Add(element.Value);
}
}
results.ToList().ForEach(async r =>
{
await lockQueue.EnqueueAsync(tx, r);
});
await tx.CommitAsync(); // This is where it gets stuck.
}
return results;
}
public async Task SetItem(IEnumerable<Product> products)
{
var productQueue = await this.StateManager.GetOrAddAsync<IReliableConcurrentQueue<Product>>("ProductQueue");
using (var tx = this.StateManager.CreateTransaction())
{
foreach (Product p in products)
{
await productQueue.EnqueueAsync(tx, p);
}
await tx.CommitAsync(); // Sometimes it gets stuck here. It's not consistent. Either in this function or the above one.
}
}
I checked this Post and tried the changes mentioned there. It doesn't still work.
Exception details:
System.TimeoutException: This can happen if message is dropped when service is busy or its long running operation and taking more time than configured Operation Timeout.
at Microsoft.ServiceFabric.Services.Communication.Client.ServicePartitionClient`1.<InvokeWithRetryAsync>d__24`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
Is there anything wrong with the way I am using the collections and the related transactions?

Breaking for or loop GWT callback response

I want to break the for loop in GWT callback's execute method response.
For Example,
for (int idx = 0; idx < recordList.getLength(); idx++) { //Starting ABC FOR LOOP
ABCDMI.addData(recordList.get(idx),
new DSCallback() {
public void execute(DSResponse response, Object rawData, DSRequest request) {
if(response.getAttribute("UnSuccess") != null && !response.getAttribute("UnSuccess").equalsIgnoreCase("")) {
break; //I want to break ABC FOR LOOP here.
}
}
}
Can anybody help me in this?
When you call an asynchronous method, you dont know how long it will take. In your examples all of these calls will be sent in almost the same instant, but the response would come in any time in the future, so the order is not guaranteed.
Of-course you cannot break a loop inside your callback, but you can handle the loop inside your callback calling the async method from it each time one call finishes.
This example should work in your case, and all callbacks would be executed sequentially.
DSCallback myCallBack = new DSCallback() {
int idx = 0;
int length = recordList.getLength();
public void execute(DSResponse response, Object rawData, DSRequest request) {
if (++idx < length
&& (response.getAttribute("UnSuccess") == null
|| !response.getAttribute("UnSuccess").equalsIgnoreCase(""))) {
ABCDMI.addData(recordList.get(idx), this);
}
}
};
ABCDMI.addData(recordList.get(0), myCallBack);

Printing using c#/ASP.NET MVC2

I am developping an intranet ASP.NET MVC2 application for a small business. The small business has multiple type of printers and depending on what is required a print request will be sent (from the browser/user) to the server and the server will dispatch the print job to the right printer accordingly. Please note that it is a completely new environment for them and I have control over pretty much everything. I will more than likely use a very lightweight OS (maybe Asus ExpressGate or Chrome OS depending on launch date?) so the users cannot have any printers installed but the server will have everything setup.
Here is my question:
Is there a simple way to print from the server side (without dialog of course because there won't be anyone waiting to click them) a page by using the html link as a parameter and keeping the HTML format of course.
I have seen a few possibilities of COM stuff out there but if there are possibilities to avoid this by using a .net class I would appreciate. I am using .net 4.0. I will however take any suggestions even if it is COM based.
Edit: Please note that any workaround that makes sense would also be taken into consideration, a quick (non studied yet) example would be to transfer this html to a doc file and sending this file to the printer.
Edit-Code taken off for lack of use.
Edits2:
Following this link: Print html document from Windows Service in C# without print dialog
Vadim's holy grail solution DOES work. However, it has limitations like using the default printer only. I am modifying the default printer before the print occurs this causes the print to go to the right printer. I can see some concurrence issues happening here but so far this is the best i came up with (most of this code is from Vadim and I give him the full credit for this):
/// <summary>Provides a scheduler that uses STA threads.</summary>
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
/// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>
private BlockingCollection<Task> _tasks;
/// <summary>The STA threads used by the scheduler.</summary>
private readonly List<Thread> _threads;
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
{
// Validate arguments
if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("concurrencyLevel");
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
{
var thread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
{
TryExecuteTask(t);
}
});
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
// Start all of the threads
_threads.ForEach(t => t.Start());
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
// Push it into the blocking collection of tasks
_tasks.Add(task);
}
/// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
/// <returns>An enumerable of all tasks currently scheduled.</returns>
protected override IEnumerable<Task> GetScheduledTasks()
{
// Serialize the contents of the blocking collection of tasks for the debugger
return _tasks.ToArray();
}
/// <summary>Determines whether a Task may be inlined.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
/// <returns>true if the task was successfully inlined; otherwise, false.</returns>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Try to inline if the current thread is STA
return
Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&
TryExecuteTask(task);
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public override int MaximumConcurrencyLevel
{
get { return _threads.Count; }
}
/// <summary>
/// Cleans up the scheduler by indicating that no more tasks will be queued.
/// This method blocks until all threads successfully shutdown.
/// </summary>
public void Dispose()
{
if (_tasks != null)
{
// Indicate that no new tasks will be coming in
_tasks.CompleteAdding();
// Wait for all threads to finish processing tasks
foreach (var thread in _threads) thread.Join();
// Cleanup
_tasks.Dispose();
_tasks = null;
}
}
}
public class PrinterHelper
{
readonly TaskScheduler _sta = new StaTaskScheduler(1);
public void PrintHtml(string htmlPath, string printerDevice)
{
if (!string.IsNullOrEmpty(printerDevice))
SetAsDefaultPrinter(printerDevice);
Task.Factory.StartNew(() => PrintOnStaThread(htmlPath), CancellationToken.None, TaskCreationOptions.None, _sta).Wait();
}
static void PrintOnStaThread(string htmlPath)
{
const short PRINT_WAITFORCOMPLETION = 2;
const int OLECMDID_PRINT = 6;
const int OLECMDEXECOPT_DONTPROMPTUSER = 2;
using(var browser = new WebBrowser())
{
browser.Navigate(htmlPath);
while(browser.ReadyState != WebBrowserReadyState.Complete)
Application.DoEvents();
dynamic ie = browser.ActiveXInstance;
ie.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER, PRINT_WAITFORCOMPLETION);
}
}
static void SetAsDefaultPrinter(string printerDevice)
{
foreach (var printer in PrinterSettings.InstalledPrinters)
{
//Verify that the printer exists here
}
var path = "win32_printer.DeviceId='" + printerDevice + "'";
using (var printer = new ManagementObject(path))
{
ManagementBaseObject outParams =
printer.InvokeMethod("SetDefaultPrinter",
null, null);
}
return;
}
}
A couple of articles for readng which maybe helpful are:
http://msdn.microsoft.com/en-us/library/system.drawing.printing.printersettings.aspx
http://msdn.microsoft.com/en-us/library/system.drawing.printing.printdocument.aspx
Will update if I find anything else. Hope this helps.
This is a parallel helper for .net 4.0 only that manages collision/threads.
/// <summary>Provides a scheduler that uses STA threads.</summary>
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
/// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>
private BlockingCollection<Task> _tasks;
/// <summary>The STA threads used by the scheduler.</summary>
private readonly List<Thread> _threads;
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
{
// Validate arguments
if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("numberOfThreads");
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
{
var thread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
{
TryExecuteTask(t);
}
}) {IsBackground = true};
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
// Start all of the threads
_threads.ForEach(t => t.Start());
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
// Push it into the blocking collection of tasks
_tasks.Add(task);
}
/// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
/// <returns>An enumerable of all tasks currently scheduled.</returns>
protected override IEnumerable<Task> GetScheduledTasks()
{
// Serialize the contents of the blocking collection of tasks for the debugger
return _tasks.ToArray();
}
/// <summary>Determines whether a Task may be inlined.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
/// <returns>true if the task was successfully inlined; otherwise, false.</returns>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Try to inline if the current thread is STA
return
Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&
TryExecuteTask(task);
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public override int MaximumConcurrencyLevel
{
get { return _threads.Count; }
}
/// <summary>
/// Cleans up the scheduler by indicating that no more tasks will be queued.
/// This method blocks until all threads successfully shutdown.
/// </summary>
public void Dispose()
{
if (_tasks != null)
{
// Indicate that no new tasks will be coming in
_tasks.CompleteAdding();
// Wait for all threads to finish processing tasks
foreach (var thread in _threads) thread.Join();
// Cleanup
_tasks.Dispose();
_tasks = null;
}
}
}
I use 2 enums, one is for the web browser control the OLECMDID:
public enum OLECMDID
{
OLECMDID_OPEN = 1,
OLECMDID_NEW = 2,
OLECMDID_SAVE = 3,
OLECMDID_SAVEAS = 4,
OLECMDID_SAVECOPYAS = 5,
OLECMDID_PRINT = 6,
OLECMDID_PRINTPREVIEW = 7,
OLECMDID_PAGESETUP = 8,
OLECMDID_SPELL = 9,
OLECMDID_PROPERTIES = 10,
OLECMDID_CUT = 11,
OLECMDID_COPY = 12,
OLECMDID_PASTE = 13,
OLECMDID_PASTESPECIAL = 14,
OLECMDID_UNDO = 15,
OLECMDID_REDO = 16,
OLECMDID_SELECTALL = 17,
OLECMDID_CLEARSELECTION = 18,
OLECMDID_ZOOM = 19,
OLECMDID_GETZOOMRANGE = 20,
OLECMDID_UPDATECOMMANDS = 21,
OLECMDID_REFRESH = 22,
OLECMDID_STOP = 23,
OLECMDID_HIDETOOLBARS = 24,
OLECMDID_SETPROGRESSMAX = 25,
OLECMDID_SETPROGRESSPOS = 26,
OLECMDID_SETPROGRESSTEXT = 27,
OLECMDID_SETTITLE = 28,
OLECMDID_SETDOWNLOADSTATE = 29,
OLECMDID_STOPDOWNLOAD = 30,
OLECMDID_FIND = 32,
OLECMDID_DELETE = 33,
OLECMDID_PRINT2 = 49,
OLECMDID_PRINTPREVIEW2 = 50,
OLECMDID_PAGEACTIONBLOCKED = 55,
OLECMDID_PAGEACTIONUIQUERY = 56,
OLECMDID_FOCUSVIEWCONTROLS = 57,
OLECMDID_FOCUSVIEWCONTROLSQUERY = 58,
OLECMDID_SHOWPAGEACTIONMENU = 59,
OLECMDID_ADDTRAVELENTRY = 60,
OLECMDID_UPDATETRAVELENTRY = 61,
OLECMDID_UPDATEBACKFORWARDSTATE = 62,
OLECMDID_OPTICAL_ZOOM = 63,
OLECMDID_OPTICAL_GETZOOMRANGE = 64,
OLECMDID_WINDOWSTATECHANGED = 65,
OLECMDID_ACTIVEXINSTALLSCOPE = 66,
OLECMDID_UPDATETRAVELENTRY_DATARECOVERY = 67
}
The other is a custom made for whatever you need to print:
public enum PrintDocumentType
{
Bill,
Label //etc...
}
Now, here is the my printer helper that I use which sets the default printer (and prints to it), also changes the margin according to what I need printed:
public class PrinterHelper
{
readonly TaskScheduler _sta = new StaTaskScheduler(1);
public void PrintHtml(string htmlPath, string printerDevice, PrintDocumentType printDocumentType)
{
if (!string.IsNullOrEmpty(printerDevice))
SetAsDefaultPrinter(printerDevice);
IeSetup(printDocumentType);
Task.Factory.StartNew(() => PrintOnStaThread(htmlPath), CancellationToken.None, TaskCreationOptions.None, _sta).Wait();
}
static void PrintOnStaThread(string htmlPath)
{
const short printWaitForCompletion = 2;
const int oleCmdExecOptDontPromptUser = 2;
using(var browser = new WebBrowser())
{
WebBrowserHelper.ClearCache(); /*needed since there is a major cache flaw. The WebBrowserHelper class is available at http://www.gutgames.com/post/Clearing-the-Cache-of-a-WebBrowser-Control.aspx with some slight changes or if website is taken off, it is based heavily on http://support.microsoft.com/kb/326201*/
browser.Navigate(htmlPath);
while(browser.ReadyState != WebBrowserReadyState.Complete)
Application.DoEvents();
dynamic ie = browser.ActiveXInstance;
((IWebBrowser2)ie).ExecWB(SHDocVw.OLECMDID.OLECMDID_PRINT, OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, oleCmdExecOptDontPromptUser, printWaitForCompletion);
}
}
static void SetAsDefaultPrinter(string printerDevice)
{
foreach (var printer in PrinterSettings.InstalledPrinters)
{
//verify that the printer exists here
}
var path = "win32_printer.DeviceId='" + printerDevice + "'";
using (var printer = new ManagementObject(path))
{
printer.InvokeMethod("SetDefaultPrinter",
null, null);
}
return;
}
/// <summary>
/// Making sure the printer doesn't output the default footer and header of Internet Explorer (url, pagenumber, title, etc.).
/// </summary>
public void IeSetup(PrintDocumentType printDocumentType)
{
const string keyName = #"Software\Microsoft\Internet Explorer\PageSetup";
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName, true)) {
if (key == null) return;
key.SetValue("footer", "");
key.SetValue("header", "");
switch (printDocumentType)
{
case PrintDocumentType.Label:
key.SetValue("margin_top", "0.12500");
key.SetValue("margin_bottom", "0.12500");
key.SetValue("margin_left", "0.25000");
key.SetValue("margin_right", "0.25000");
break;
case PrintDocumentType.Bill:
key.SetValue("margin_top", "0.75000");
key.SetValue("margin_bottom", "0.75000");
key.SetValue("margin_left", "0.75000");
key.SetValue("margin_right", "0.75000");
break;
}
}
}
}
As you notice, I have a webbrowserhelper that is another class. It is quite big and I will not paste it. However, I input the links in comment next to it where you can get the code. The webbrowser itself has a major cache flaw and even if you force it to refresh the page it will always grab the cache, therefore, a clearcache is in order. I learned the hard way.
I hope this helps everyone out there. Please note that this is for .net 4.0.