async void connectButton_Click(object sender, EventArgs e) { connectButton.Enabled = false; var options = new WorkerOptions { Tubes = { watchTextBox.Text.Trim() } }; subscription = await BeanstalkConnection.ConnectWorkerAsync(_connectionString, options, async (conn, job) => { jobs.Add(job); await conn.DeleteAsync(); await Task.Delay(1000); }); disconnectButton.Enabled = true; }
/// <summary> /// Schedules a worker with a dedicated TCP connection to repeatedly reserve jobs /// from the specified tubes and process them. /// </summary> /// <param name="configuration">The configuration for the Beanstalk connection.</param> /// <param name="options">The worker options.</param> /// <param name="worker">The delegate used to processed reserved jobs.</param> /// <returns>A token which stops the worker when disposed.</returns> public static Task <IDisposable> ConnectWorkerAsync <T>(ConnectionConfiguration configuration, WorkerOptions options, Func <IWorker, Job <T>, Task> worker) { WorkerFunc workerFunc = (w, untypedJob) => { // If the deserializer throws, this will be handled like any other failure of the WorkerFunc. var obj = configuration.JobSerializer.Deserialize <T>(untypedJob.Data); var typedJob = new Job <T>(untypedJob.Id, untypedJob.Data, obj); return(worker(w, typedJob)); }; return(ConnectWorkerAsync(configuration, options, workerFunc)); }
async Task WorkerLoop(WorkerFunc workerFunc, WorkerOptions options, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { var job = await ReserveAsync(cancellationToken).ConfigureAwait(false); if (job == null) { continue; // DEADLINE_SOON } try { var worker = new Worker(job, this); // Details: http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html await Task.Factory.StartNew( () => workerFunc(worker, job), cancellationToken, TaskCreationOptions.DenyChildAttach, options.TaskScheduler) .Unwrap() .ConfigureAwait(false); if (worker.Completed) { continue; } // else, fall through to the failure handling } catch (Exception) { // Carry on outside the catch... } // Failure handling // Either the workerFunc threw or didn't delete/release/bury the job var cons = this as IConsumer; int priority; switch (options.FailureBehavior) { case WorkerFailureBehavior.Bury: priority = options.FailurePriority ?? (await cons.JobStatisticsAsync(job.Id).ConfigureAwait(false)).Priority; await cons.BuryAsync(job.Id, priority).ConfigureAwait(false); continue; case WorkerFailureBehavior.Release: priority = options.FailurePriority ?? (await cons.JobStatisticsAsync(job.Id).ConfigureAwait(false)).Priority; await cons.ReleaseAsync(job.Id, priority, options.FailureReleaseDelay).ConfigureAwait(false); continue; case WorkerFailureBehavior.Delete: await cons.DeleteAsync(job.Id).ConfigureAwait(false); continue; case WorkerFailureBehavior.NoAction: continue; default: throw new InvalidOperationException("Unhandled WorkerFailureBehavior '" + options.FailureBehavior.ToString() + "'"); } } }
/// <summary> /// Schedules a worker with a dedicated TCP connection to repeatedly reserve jobs /// from the specified tubes and process them. /// </summary> /// <param name="connectionString">The connection string.</param> /// <param name="options">The worker options.</param> /// <param name="worker">The delegate used to processed reserved jobs.</param> /// <returns>A token which stops the worker when disposed.</returns> public static Task <IDisposable> ConnectWorkerAsync(string connectionString, WorkerOptions options, WorkerFunc worker) { return(ConnectWorkerAsync(ConnectionConfiguration.Parse(connectionString), options, worker)); }
/// <summary> /// Schedules a worker with a dedicated TCP connection to repeatedly reserve jobs /// from the specified tubes and process them. /// </summary> /// <param name="configuration">The configuration for the Beanstalk connection.</param> /// <param name="options">The worker options.</param> /// <param name="worker">The delegate used to processed reserved jobs.</param> /// <returns>A token which stops the worker when disposed.</returns> public static async Task <IDisposable> ConnectWorkerAsync(ConnectionConfiguration configuration, WorkerOptions options, WorkerFunc worker) { // Must capture the context before the first await if (options.TaskScheduler == null) { options.TaskScheduler = SynchronizationContext.Current == null ? TaskScheduler.Default : TaskScheduler.FromCurrentSynchronizationContext(); } var conn = await BeanstalkConnection.ConnectAsync(configuration).ConfigureAwait(false); try { // Just take the default tube if none was given if (options.Tubes.Count > 0) { foreach (var tube in options.Tubes) { await((IConsumer)conn).WatchAsync(tube).ConfigureAwait(false); } if (!options.Tubes.Contains("default")) { await((IConsumer)conn).IgnoreAsync("default").ConfigureAwait(false); } } } catch { conn.Dispose(); throw; } var cts = new CancellationTokenSource(); var disposable = Disposable.Create(() => { cts.Cancel(); cts.Dispose(); conn.Dispose(); }); #pragma warning disable 4014 Task[] workerLoops = new Task[options.NumberOfWorkers]; for (var i = 0; i < options.NumberOfWorkers; i++) { workerLoops[i] = conn.WorkerLoop(worker, options, cts.Token); } // Ensure we handle any thrown exceptions Task.WhenAll(workerLoops).ContinueWith(t => { disposable.Dispose(); if (t.Exception != null) { t.Exception.Handle(ex => true); } }, TaskContinuationOptions.OnlyOnFaulted); #pragma warning restore 4014 return(disposable); }