So I need to have a timer to count down from 60 seconds. I am new to Xamarin and have no idea what it accepts. It will be used in Android.
Any suggestions on how to start?
Can you use System.Timers.Timer?
You can use the System.Threading.Timer class, which is documented in the Xamarin docs: https://developer.xamarin.com/api/type/System.Threading.Timer/
Alternatively, for Xamarin.Forms, you have access to a cross-platform Timer via the Device class:
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
// called every 1 second
// do stuff here
return true; // return true to repeat counting, false to stop timer
});
If you just need Android you can use
System.Threading.Timer
In shared code with Xamarin Forms you can use
Device.StartTimer(...)
Or you can implement one yourselfe with advanced features like:
public sealed class Timer : CancellationTokenSource {
private readonly Action _callback;
private int _millisecondsDueTime;
private readonly int _millisecondsPeriod;
public Timer(Action callback, int millisecondsDueTime) {
_callback = callback;
_millisecondsDueTime = millisecondsDueTime;
_millisecondsPeriod = -1;
Start();
}
public Timer(Action callback, int millisecondsDueTime, int millisecondsPeriod) {
_callback = callback;
_millisecondsDueTime = millisecondsDueTime;
_millisecondsPeriod = millisecondsPeriod;
Start();
}
private void Start() {
Task.Run(() => {
if (_millisecondsDueTime <= 0) {
_millisecondsDueTime = 1;
}
Task.Delay(_millisecondsDueTime, Token).Wait();
while (!IsCancellationRequested) {
//TODO handle Errors - Actually the Callback should handle the Error but if not we could do it for the callback here
_callback();
if(_millisecondsPeriod <= 0) break;
if (_millisecondsPeriod > 0 && !IsCancellationRequested) {
Task.Delay(_millisecondsPeriod, Token).Wait();
}
}
});
}
public void Stop() {
Cancel();
}
protected override void Dispose(bool disposing) {
if (disposing) {
Cancel();
}
base.Dispose(disposing);
}
}
Yes, you can use System.Timers.Timer.
In Android we can use java.util.Timer like this:
private int i;
final Timer timer=new Timer();
TimerTask task=new TimerTask() {
#Override
public void run() {
if (i < 60) {
i++;
} else {
timer.cancel();
}
Log.e("time=",i+"");
}
};
timer.schedule(task, 0,1000);
In Xamarin.Android we can also use java.util.Timer like this:
[Activity(Label = "Tim", MainLauncher = true)]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
Timer timer = new Timer();
timer.Schedule(new MyTask(timer),0,1000);
}
}
class MyTask : TimerTask
{
Timer mTimer;
public MyTask(Timer timer) {
this.mTimer = timer;
}
int i;
public override void Run()
{
if (i < 60)
{
i++;
}
else {
mTimer.Cancel();
}
Android.Util.Log.Error("time",i+"");
}
}
You can always use
Task.Factory.StartNewTaskContinuously(YourMethod, new CancellationToken(), TimeSpan.FromMinutes(10));
Be sure to add the following class to your project
static class Extensions
{
/// <summary>
/// Start a new task that will run continuously for a given amount of time.
/// </summary>
/// <param name="taskFactory">Provides access to factory methods for creating System.Threading.Tasks.Task and System.Threading.Tasks.Task`1 instances.</param>
/// <param name="action">The action delegate to execute asynchronously.</param>
/// <param name="cancellationToken">The System.Threading.Tasks.TaskFactory.CancellationToken that will be assigned to the new task.</param>
/// <param name="timeSpan">The interval between invocations of the callback.</param>
/// <returns>The started System.Threading.Tasks.Task.</returns>
public static Task StartNewTaskContinuously(this TaskFactory taskFactory, Action action, CancellationToken cancellationToken, TimeSpan timeSpan
, string taskName = "")
{
return taskFactory.StartNew(async () =>
{
if (!string.IsNullOrEmpty(taskName))
{
Debug.WriteLine("Started task " + taskName);
}
while (!cancellationToken.IsCancellationRequested)
{
action();
try
{
await Task.Delay(timeSpan, cancellationToken);
}
catch (Exception e)
{
break;
}
}
if (!string.IsNullOrEmpty(taskName))
{
Debug.WriteLine("Finished task " + taskName);
}
});
}
}
Related
Hello I have a Blazor server app where some PLC variables are read from a remote production machine.
The code part that is connecting to the PLC and reading the datas is in a sourced service (PLCService.cs). The service is injected and called in a razor page.
If I trigger the reading manualy with a button, all the variables are read correctly. So far no problem. But I want that the variables are read every second from the remote machine's PLC. Therefore I have programmed a timer in the codebehind page of my razor page (not in the service), but the timer is not working. (The values are note read even once)
In the razor page there are also the variables that I read from the PLC, but for making it leaner I have just shown the counter value, that should count each second.
In my razor file:
#inject PLCService PLCService
<button #onclick="Read_CNC_Status">Read PLC Data</button>
<l>#counter_timer</l> // Unfortunately the counter value is always "0"
In my razor.cs file:
using System.Timers;
public partial class Read_PLC_Data
{
public int counter_timer=0;
System.Timers.Timer timer1 = new System.Timers.Timer();
public async void Read_CNC_Status()
{
PLCService.Connect_PLC(); // Connection code is in a sourced service
Initialise_Timer1();
}
public void Initialise_Timer1()
{
timer1.Elapsed += new ElapsedEventHandler(OnTimedEvent1);
timer1.Interval = 1000;
timer1.Enabled = true;
counter_timer = 0;
}
public void OnTimedEvent1(object source, ElapsedEventArgs e)
{
PLCService.Read_PLC_Data();
counter_CNC_status += 1; // This counter is not counting !!!
if(counter_timer >= 30)
{
counter_timer = 0;
timer1.Enabled = false;
}
StateHasChanged();
}
}
Try the periodic timer:
private readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(5));
public async void OnGet()
{
while(await _periodicTimer.WaitForNextTickAsync())
{
await ConnectPlc();
}
}
UPDATE 1
If you want to go with your approach you should use InvokeAsync(StateHasChanged) because Blazor would not recognize the state change and not refresh the UI
<h3>#_currentCount</h3>
#code {
private int _currentCount;
private System.Timers.Timer _timer;
protected override void OnInitialized()
{
_timer = new();
_timer.Interval = 1000;
_timer.Elapsed += async (object? sender, ElapsedEventArgs e) =>
{
_currentCount++;
await InvokeAsync(StateHasChanged);
};
_timer.Enabled = true;
}
}
Don't forget to invoke the StateHasChanged method
UPDATE 2
If you want to use the periodic timer that i initially suggest, you can use it this way:
First let's assume that you have a class that is responsible for PLC data and implements the INotifyPropertyChanged interface:
public class PlcData : INotifyPropertyChanged
{
private readonly ILogger<PlcData> _logger;
public event PropertyChangedEventHandler? PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _counter;
public int Counter
{
get { return _counter; }
set
{
if (_counter != value)
{
_counter = value;
NotifyPropertyChanged(nameof(Counter));
}
}
}
public PlcData(ILogger<PlcData> logger)
{
_logger = logger;
}
public async Task GetFromPlcAsync()
{
_logger.LogInformation("Get new info: {c}", ++Counter);
await Task.CompletedTask;
}
}
Then Create a background service
public class PlcService : BackgroundService
{
private readonly PlcData _plc;
private readonly PeriodicTimer _timer;
public PlcService(PlcData plc)
{
_plc = plc;
_timer = new(TimeSpan.FromSeconds(1));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while(await _timer.WaitForNextTickAsync(stoppingToken)
&& !stoppingToken.IsCancellationRequested)
{
await _plc.GetFromPlcAsync();
}
}
}
In razor page your need to inject the PlcData
#inject PlcData plcData
<h3>#_currentCount</h3>
#code {
private static int _currentCount;
protected override void OnInitialized()
{
plcData.PropertyChanged += OnIncrement;
}
private async void OnIncrement(object? sender, PropertyChangedEventArgs e)
{
_currentCount = plcData.Counter;
await InvokeAsync(() =>
{
StateHasChanged();
});
}
}
Also you need to add services to the container.
builder.Services.AddHostedService<PlcService>();
...
...
builder.Services.AddSingleton<PlcData>();
Here is a typical Program.cs from .net6 Blazor-Server And that's it!
UPDATE 3
Using System.Threading
#page "/"
#using System.Threading;
<h3>#_currentCount</h3>
#code {
private int _currentCount;
protected override void OnInitialized()
{
var timer = new Timer(new TimerCallback(_ =>
{
_currentCount++;
InvokeAsync(() =>
{
StateHasChanged();
});
}), null, 1000, 1000);
}
}
I don't see where you are calling the Read_CNC_Status() method. If you don't call it, then nothing you wrote about the timer is ever executed. You can call it in the component's OnAfterRender function (or in the constructor of your partial class).
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Read_CNC_Status();
}
base.OnAfterRender(firstRender);
}
I ran the rest of the code and it works.
EDIT: It's not necessary, but I also recommend that you make sure the handler is not called again before the previous execution is finished (this could happen for example if Read_PLC_Data() is slow and takes more than the timer's Interval to complete). To do that, you can set the AutoReset property of your timer to false and manually restart the timer each time at the end of your handler, like this:
public async void Read_CNC_Status()
{
PLCService.Connect_PLC(); // Connection code is in a sourced service
Initialise_Timer1();
}
public void Initialise_Timer1()
{
timer1.Elapsed += new ElapsedEventHandler(OnTimedEvent1);
timer1.Interval = 1000;
timer1.AutoReset = false;
counter_CNC_status = 0;
timer1.Start();
}
public void OnTimedEvent1(object source, ElapsedEventArgs e)
{
try
{
PLCService.Read_PLC_Data();
counter_CNC_status += 1; // This counter is not counting !!!
}
finally
{
if(counter_CNC_status >= 30)
{
counter_CNC_status = 0;
timer1.Enabled = false;
}
else
{
timer1.Start();
}
}
StateHasChanged();
}
The best approach for me is to use QUARTZ.NET
I created a very basic Service for dictionary playback using TTS (see the complete code below) and running into the same issue on all my 3 android devices (android versions 5, 7 and 8).
Gist: The app plays vocabulary entries, definitions and examples. Between each of them the app takes pause.
Symptoms:
The issue is happening mostly when I use 8 seconds for pause and the app is in the background mode (the screen is turned off). The playback simply gets frozen.
Sometimes the playback continues on its own with screen turned off after a lengthy pause sometimes being up to 20 - 30 minutes or even longer (but then the next entry is played after a very lenghty pause too, provided that we haven't activated screen). Could be some other process partly waking the phone?
Also, playback continues straight after I pressed Power button and screen turns on.
Debug info:
I was reckoning to press pause in Visual Studio after the app got frozen in order to see which bit of code is the cause - unfortunately the debugger seems to keep the device awake and this issue is extremely difficult to reveal.
In order to prevent my app from being frozen I acquire Partial WakeLock in my service (but this still doesn't help, even though app manifest contains permission for WAKE_LOCK)
private void AcquireWakeLock(MainActivity activity)
{
var mgr = (PowerManager)activity.ApplicationContext.GetSystemService(Context.PowerService);
WakeLock = mgr.NewWakeLock(WakeLockFlags.Partial, "myWakeLock");
WakeLock.Acquire();
}
My app also has Play/Pause button and I use TaskCompletionSource for the app to wait until I resume playback
public async Task PlayPause(bool isChecked, MainActivity mainActivity)
{
if (isChecked)
{
ReleaseWakeLock();
AppSuspended = new TaskCompletionSource<bool>();
Tts.Stop();
}
else
{
AcquireWakeLock(mainActivity);
AppSuspended.TrySetResult(true);
}
}
Then, before each next word/phrase is about to be played I use the following code for my app to wait for my resuming playback
await AppSuspended.Task;
Complete code
[Service(Name = "com.my_app.service.PlaybackService")]
public class PlaybackService : Service, TextToSpeech.IOnInitListener, TextToSpeech.IOnUtteranceCompletedListener
{
public IBinder Binder { get; private set; }
private Java.Util.Locale Lang;
private bool Playing;
private int EntryIndex;
private int DefinitionIndex;
private DictionaryDto Dictionary;
private EntryDto CurrentEntry;
private DefinitionDto CurrentDefinition;
private TaskCompletionSource<bool> AppSuspended;
protected TextToSpeech Tts;
private TaskCompletionSource<bool> PlaybackFinished;
private WakeLock WakeLock;
public override void OnCreate()
{
base.OnCreate();
Tts = new TextToSpeech(this, this);
Lang = Tts.DefaultLanguage;
AppSuspended = new TaskCompletionSource<bool>();
AppSuspended.TrySetResult(true);
}
public override IBinder OnBind(Intent intent)
{
Binder = new PlaybackBinder(this);
return Binder;
}
public override bool OnUnbind(Intent intent)
{
return base.OnUnbind(intent);
}
public override void OnDestroy()
{
Binder = null;
base.OnDestroy();
}
void TextToSpeech.IOnUtteranceCompletedListener.OnUtteranceCompleted(string utteranceId)
{
if (utteranceId.Equals("PlaybackFinished")) { PlaybackFinished.TrySetResult(true); }
}
void TextToSpeech.IOnInitListener.OnInit(OperationResult status)
{
// if we get an error, default to the default language
if (status == OperationResult.Error)
Tts.SetLanguage(Java.Util.Locale.Default);
// if the listener is ok, set the lang
if (status == OperationResult.Success)
{
Tts.SetLanguage(Lang);
Tts.SetOnUtteranceCompletedListener(this);
}
}
public async Task Play(string text)
{
Dictionary<string, string> myHashRender = new Dictionary<string, string>();
myHashRender.Add(TextToSpeech.Engine.KeyParamUtteranceId, "PlaybackFinished");
PlaybackFinished = new TaskCompletionSource<bool>();
Tts.Speak(text, QueueMode.Flush, myHashRender);
await PlaybackFinished.Task;
}
public async Task PlaySilence(long ms)
{
Dictionary<string, string> myHashRender = new Dictionary<string, string>();
myHashRender.Add(TextToSpeech.Engine.KeyParamUtteranceId, "PlaybackFinished");
PlaybackFinished = new TaskCompletionSource<bool>();
Tts.PlaySilence(ms, QueueMode.Flush, myHashRender);
await PlaybackFinished.Task;
}
private async Task PlayDictionary(MainActivity activity)
{
EntryIndex = 0;
for (; EntryIndex < Dictionary.Entries.Count;)
{
CurrentEntry = Dictionary.Entries.ElementAt(EntryIndex);
await AppSuspended.Task;
if (!Playing) { return; }
if (!string.IsNullOrEmpty(CurrentEntry.Text))
{
await AppSuspended.Task;
if (!Playing) { return; }
await Play(CurrentEntry.Text);
}
DefinitionIndex = 0;
for (; DefinitionIndex < CurrentEntry.Definitions.Count();)
{
CurrentDefinition = CurrentEntry.Definitions.ElementAt(DefinitionIndex);
await PlayDefinition();
await PlayExamples();
DefinitionIndex++;
}
if (Playing)
{
DefinitionIndex++;
}
EntryIndex++;
}
}
private async Task PlayExamples()
{
if (!Playing) { return; }
foreach (var example in CurrentDefinition.Examples)
{
if (!string.IsNullOrEmpty(example))
{
await AppSuspended.Task;
if (!Playing) { return; }
await Play(example);
if (Playing)
{
await PlaySilence((long)TimeSpan.FromSeconds(8).TotalMilliseconds);
}
}
}
}
private async Task PlayDefinition()
{
if (!Playing) { return; }
if (!string.IsNullOrEmpty(CurrentEntry.Definitions.ElementAt(DefinitionIndex).Text))
{
await AppSuspended.Task;
if (!Playing) { return; }
await PlayDefinitionText();
if (Playing)
{
await PlaySilence((long)TimeSpan.FromSeconds(7).TotalMilliseconds);
}
}
}
private async Task PlayDefinitionText()
{
await AppSuspended.Task;
await Play($"{CurrentEntry.Definitions.ElementAt(DefinitionIndex).Text}");
}
private void ReleaseWakeLock()
{
if (WakeLock != null)
{
WakeLock.Release();
}
}
private void AcquireWakeLock(MainActivity activity)
{
var mgr = (PowerManager)activity.ApplicationContext.GetSystemService(Context.PowerService);
WakeLock = mgr.NewWakeLock(WakeLockFlags.Partial, "myWakeLock");
WakeLock.Acquire();
}
public async Task PlayPause(bool isChecked, MainActivity mainActivity)
{
if (isChecked)
{
ReleaseWakeLock();
AppSuspended = new TaskCompletionSource<bool>();
Tts.Stop();
}
else
{
AcquireWakeLock(mainActivity);
AppSuspended.TrySetResult(true);
}
}
}
Additional info:
The issue happens on all of my devices
Galaxy C7 (Oreo)
Galaxy Tab A3 (Nougat)
Galaxy A3 (Lollipop)
I investigated the issue thoroughly and followed the recommendation to switch to Foreground Service which solved my problem perfectly.
Tested with Lollipop, Nougat, Oreo.
Foreground Service aproach
Put the following method in your MainActivity class
public void StartForegroundServiceSafely(Intent intent)
{
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
StartForegroundService(intent);
}
else
{
StartService(intent);
}
}
You then start your service via Intent
public void PlayFromFile(Android.Net.Uri uri)
{
AcquireWakeLock();
Intent startIntent = new Intent(this, typeof(PlaybackService));
startIntent.SetAction(PlaybackConsts.Start);
startIntent.PutExtra("uri", uri.ToString());
StartForegroundServiceSafely(startIntent);
}
Implement OnStartCommand method in your service
public class PlaybackService : Service, TextToSpeech.IOnInitListener, TextToSpeech.IOnUtteranceCompletedListener
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
if (intent.Action.Equals(PlaybackConsts.Start))
{
var notification =
new Notification.Builder(this)
.SetContentTitle(Resources.GetString(Resource.String.ApplicationName))
.SetContentText("HELLO WORLD")
.SetOngoing(true)
.Build();
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
}
if (intent.Action.Equals(PlaybackConsts.Start))
{
var uri = Android.Net.Uri.Parse(intent.GetStringExtra("uri"));
var content = MiscellaneousHelper.GetTextFromStream(ContentResolver.OpenInputStream(uri));
Dictionary = DictionaryFactory.Get(content);
Playing = true;
Task.Factory.StartNew(async () =>
{
await PlayDictionary();
});
}
if (intent.Action.Equals(PlaybackConsts.PlayPause))
{
bool isChecked = intent.GetBooleanExtra("isChecked", false);
PlayPause(isChecked);
}
if (intent.Action.Equals(PlaybackConsts.NextEntry))
{
NextEntry();
}
if (intent.Action.Equals(PlaybackConsts.PrevEntry))
{
PrevEntry();
}
if (intent.Action.Equals(PlaybackConsts.Stop))
{
Task.Factory.StartNew(async () =>
{
await Stop();
});
StopForeground(true);
StopSelf();
}
return StartCommandResult.Sticky;
}
From the code above we've learned how to trigger service's functionality in OnStartCommand method.
How to broadcast events from Service
Define your BroadcastReceiver
[BroadcastReceiver(Enabled = true, Exported = false)]
public class PlaybackBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
var activity = MainActivity.GetInstance(); // if you need your activity here, see further code below
if (intent.Action == "renderEntry")
{
string entryHtml = intent.GetStringExtra("html");
// omitting code to keep example concise
}
}
}
Declare receiver field in your MainActivity class.
Also encase you need your activity in BroadcastReceiver class you can declare GetInstance method (singleton approach).
public class MainActivity : AppCompatActivity
{
PlaybackBroadcastReceiver receiver;
protected DrawerLayout drawerLayout;
protected NavigationView navigationView;
protected WakeLock WakeLock;
private static MainActivity instance;
public static MainActivity GetInstance()
{
return instance;
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
receiver = new PlaybackBroadcastReceiver();
instance = this;
}
protected override void OnStart()
{
base.OnStart();
RegisterReceiver(receiver, new IntentFilter("renderEntry"));
}
In order to unregister receiver use the following line:
UnregisterReceiver(receiver);
Broadcasting events from service
In your service you must also use intent
private void SendRenderEntryBroadcast(EntryDto entry)
{
Intent intent = new Intent("renderEntry");
intent.PutExtra("html", GetEntryHtml(entry));
SendBroadcast(intent);
}
I have a method that send some SMS to our customers that look like below:
public void ProccessSmsQueue()
{
SmsDbContext context = new SmsDbContext();
ISmsProvider provider = new ZenviaProvider();
SmsManager manager = new SmsManager(context, provider);
try
{
manager.ProcessQueue();
}
catch (Exception ex)
{
EventLog.WriteEntry(ex.Message, EventLogEntryType.Error);
}
finally
{
context.Dispose();
}
}
protected override void OnStart(string[] args)
{
Task.Factory.StartNew(DoWork).ContinueWith( ??? )
}
So, I have some issues:
I donĀ“t know how long it takes for the method run;
The method can throw exceptions, that I want to write on EventLog
I want to run this method in loop, every 10 min, but only after last execution finish.
How I can achieve this? I thought about using ContinueWith(), but I still have questions on how to build the entire logic.
You should have an async method that accepts a CancellationToken so it knows when to stop, calls ProccessSmsQueue in a try-catch block and uses Task.Delay to asynchronously wait until the next time it needs to run:
public async Task DoWorkAsync(CancellationToken token)
{
while (true)
{
try
{
ProccessSmsQueue();
}
catch (Exception e)
{
// Handle exception
}
await Task.Delay(TimeSpan.FromMinutes(10), token);
}
}
You can call this method when your application starts and Task.Wait the returned task before existing so you know it completes and has no exceptions:
private Task _proccessSmsQueueTask;
private CancellationTokenSource _cancellationTokenSource;
protected override void OnStart(string[] args)
{
_cancellationTokenSource = new CancellationTokenSource();
_proccessSmsQueueTask = Task.Run(() => DoWorkAsync(_cancellationTokenSource.Token));
}
protected override void OnStop()
{
_cancellationTokenSource.Cancel();
try
{
_proccessSmsQueueTask.Wait();
}
catch (Exception e)
{
// handle exeption
}
}
Sample Worker Class that I have used in Windows Services. It supports stopping in a 'clean' way by using a lock.
You just have to add your code in DoWork, set your timer in the StartTimerAndWork method (in milliseconds), and use this class in your service.
public class TempWorker
{
private System.Timers.Timer _timer = new System.Timers.Timer();
private Thread _thread = null;
private object _workerStopRequestedLock = new object();
private bool _workerStopRequested = false;
private object _loopInProgressLock = new object();
private bool _loopInProgress = false;
bool LoopInProgress
{
get
{
bool rez = true;
lock (_loopInProgressLock)
rez = _loopInProgress;
return rez;
}
set
{
lock (_loopInProgressLock)
_loopInProgress = value;
}
}
#region constructors
public TempWorker()
{
}
#endregion
#region public methods
public void StartWorker()
{
lock (_workerStopRequestedLock)
{
this._workerStopRequested = false;
}
_thread = new Thread(new ThreadStart(StartTimerAndWork));
_thread.Start();
}
public void StopWorker()
{
if (this._thread == null)
return;
lock (_workerStopRequestedLock)
this._workerStopRequested = true;
int iter = 0;
while (LoopInProgress)
{
Thread.Sleep(100);
iter++;
if (iter == 60)
{
_thread.Abort();
}
}
//if (!_thread.Join(60000))
// _thread.Abort();
}
#endregion
#region private methods
private void StartTimerAndWork()
{
this._timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
this._timer.Interval = 10000;//milliseconds
this._timer.Enabled = true;
this._timer.Start();
}
#endregion
#region event handlers
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (!LoopInProgress)
{
lock (_workerStopRequestedLock)
{
if (this._workerStopRequested)
{
this._timer.Stop();
return;
}
}
DoWork();
}
}
private void DoWork()
{
try
{
this.LoopInProgress = true;
//DO WORK HERE
}
catch (Exception ex)
{
//LOG EXCEPTION HERE
}
finally
{
this.LoopInProgress = false;
}
}
#endregion
}
I have a producer/consumer queue as following but I am getting ArgumentWException.
Following is the code:
public class ProducerConsumer<T> where T : class
{
#region Private Variables
private Thread _workerThread;
private readonly Queue<T> _workQueue;
private object _enqueueItemLocker = new object();
private object _processRecordLocker = new object();
private readonly Action<T> _workCallbackAction;
private AutoResetEvent _workerWaitSignal;
#endregion
#region Constructor
public ProducerConsumer(Action<T> action)
{
_workQueue = new Queue<T>();
_workCallbackAction = action;
}
#endregion
#region Private Methods
private void ProcessRecord()
{
while (true)
{
T workItemToBeProcessed = default(T);
bool hasSomeWorkItem = false;
lock (_processRecordLocker)
{
hasSomeWorkItem = _workQueue.Count > 0;
if (hasSomeWorkItem)
{
workItemToBeProcessed = _workQueue.Dequeue();
if (workItemToBeProcessed == null)
{
return;
}
}
}
if (hasSomeWorkItem)
{
if (_workCallbackAction != null)
{
_workCallbackAction(workItemToBeProcessed);
}
}
else
{
_workerWaitSignal.WaitOne();
}
}
}
#endregion
#region Public Methods
/// <summary>
/// Enqueues work item in the queue.
/// </summary>
/// <param name="workItem">The work item.</param>
public void EnQueueWorkItem(T workItem)
{
lock (_enqueueItemLocker)
{
_workQueue.Enqueue(workItem);
if (_workerWaitSignal == null)
{
_workerWaitSignal = new AutoResetEvent(false);
}
_workerWaitSignal.Set();
}
}
/// <summary>
/// Stops the processer, releases wait handles.
/// </summary>
/// <param name="stopSignal">The stop signal.</param>
public void StopProcesser(AutoResetEvent stopSignal)
{
EnQueueWorkItem(null);
_workerThread.Join();
_workerWaitSignal.Close();
_workerWaitSignal = null;
if (stopSignal != null)
{
stopSignal.Set();
}
}
/// <summary>
/// Starts the processer, starts a new worker thread.
/// </summary>
public void StartProcesser()
{
if (_workerWaitSignal == null)
{
_workerWaitSignal = new AutoResetEvent(false);
}
_workerThread = new Thread(ProcessRecord) { IsBackground = true };
_workerThread.Start();
}
#endregion
}
Another class is:
public class Tester
{
private readonly ProducerConsumer<byte[]> _proConsumer;
public Tester()
{
_proConsumer = new ProducerConsumer<byte[]>(Display);
}
public void AddData(byte[] data)
{
try
{
_proConsumer.EnQueueWorkItem(recordData);
}
catch (NullReferenceException nre)
{
}
}
public void Start()
{
_proConsumer.StartProcesser();
}
private static object _recordLocker = new object();
private void Display(byte[] recordByteStream)
{
try
{
lock (_recordLocker)
{
Console.WriteLine("Done with data:" + BitConverter.ToInt32(recordByteStream, 0));
}
}
catch (Exception ex)
{
}
}
}
And my main function:
class Program
{
private static Tester _recorder;
static void Main(string[] args)
{
_recorder = new Tester();
_recorder.StartRecording();
for (int i = 0; i < 100000; i++)
{
_recorder.AddRecordData(BitConverter.GetBytes(i));
}
Console.Read();
}
}
Any idea why do I get the exception and what should I do to avoid that ?
Your class, in its current implementation, is not thread-safe. You're using two different objects for your Enqueue (lock (_enqueueItemLocker)) and Dequeue (lock (_processRecordLocker)) calls, which creates a race condition in your Queue<T>.
You need to lock the same object instance on both calls in order to safely use the queue.
If you're using .NET 4, I'd recommend either using ConcurrentQueue<T> or BlockingCollection<T> instead, as these would eliminate the need for the locks in your code, since they're thread-safe.
I've got an app that has to do the following type of things, preferably on the GUI thread since that's where most of the action is taking place and there's no long-running ops:
Wait 1000
FuncA()
Wait 2000
FuncB()
Wait 1000
FuncC()
I realize I could use a timer with a state-machine style OnTick function, but that seems cumbersome:
int _state;
void OnTick(object sender, EventArgs e) {
switch (_state) {
case 0:
FuncA();
_timer.Interval = TimeSpan.FromSeconds(2);
_state = 1;
break;
case 1:
FuncB();
_timer.Interval = TimeSpan.FromSeconds(1);
_state = 2;
break;
case 2:
FuncC();
_timer.IsEnabled = false;
_state = 0;
}
}
Plus I'd like to be able to make it generic enough to do something like
RunSequenceOnGuiThread(new Sequence {
{1000, FuncA}
{2000, FuncB}
{1000, FuncC}};
Is there an idiomatic way to do this kind of thing? Given all the TPL stuff, or Rx, or even the computation expressions in F# I'd assume one exists, but I'm not finding it.
Observable.Concat(
Observer.Timer(1000).Select(_ => Func1()),
Observer.Timer(2000).Select(_ => Func2()),
Observer.Timer(1000).Select(_ => Func3()))
.Repeat()
.Subscribe();
The only thing you have to do to make this work, is make sure that your Func's return a value (even if that value is Unit.Default, i.e. nothing)
Edit: Here's how to make a generic version:
IObservable<Unit> CreateRepeatingTimerSequence(IEnumerable<Tuple<int, Func<Unit>>> actions)
{
return Observable.Concat(
actions.Select(x =>
Observable.Timer(x.Item1).Select(_ => x.Item2())))
.Repeat();
}
Here's a sketch of this in F#:
let f() = printfn "f"
let g() = printfn "g"
let h() = printfn "h"
let ops = [
1000, f
2000, g
1000, h
]
let runOps ops =
async {
for time, op in ops do
do! Async.Sleep(time)
op()
} |> Async.StartImmediate
runOps ops
System.Console.ReadKey() |> ignore
That's in a console app, but you can just call runOps on the GUI thread. See also this blog.
If you're using VS11/NetFx45/C#5, you can do a similar thing with C# async/await and a List of Tuple of Action delegates.
using the async CTP or .NET 4.5 (C# 5) it's REALLY easy using an async method and the await operator. This can be called directly on the UI thread and it will work as expected.
public async void ExecuteStuff()
{
await TaskEx.Delay(1000);
FuncA();
await TaskEx.Delay(2000);
FuncB();
await TaskEx.Delay(1000);
FuncC();
}
Here's a way to combine "yield return" and the reactive framework to give you a "poor man's async". Basically lets you "await" any IObservable. Here I just use it for timers since that's what you were interested in, but it you can have it "await" button clicks (using a Subject<Unit>) etc before moving on to the next thing as well.
public sealed partial class Form1 : Form {
readonly Executor _executor = new Executor();
public Form1() {
InitializeComponent();
_executor.Run(CreateAsyncHandler());
}
IEnumerable<IObservable<Unit>> CreateAsyncHandler() {
while (true) {
var i = 0;
Text = (++i).ToString();
yield return WaitTimer(500);
Text = (++i).ToString();
yield return WaitTimer(500);
Text = (++i).ToString();
yield return WaitTimer(500);
Text = (++i).ToString();
}
}
IObservable<Unit> WaitTimer(double ms) {
return Observable.Timer(TimeSpan.FromMilliseconds(ms), new ControlScheduler(this)).Select(_ => Unit.Default);
}
}
public sealed class Executor {
IEnumerator<IObservable<Unit>> _observables;
IDisposable _subscription = new NullDisposable();
public void Run(IEnumerable<IObservable<Unit>> actions) {
_observables = (actions ?? new IObservable<Unit>[0]).Concat(new[] {Observable.Never<Unit>()}).GetEnumerator();
Continue();
}
void Continue() {
_subscription.Dispose();
_observables.MoveNext();
_subscription = _observables.Current.Subscribe(_ => Continue());
}
public void Stop() {
Run(null);
}
}
sealed class NullDisposable : IDisposable {
public void Dispose() {}
}
It's a slight modification of Daniel Earwicker's AsyncIOPipe idea: http://smellegantcode.wordpress.com/2008/12/05/asynchronous-sockets-with-yield-return-of-lambdas/
Interesting all the different responses. Here's a simple DIY option that doesn't depend on any other libraries, and doesn't hog thread resources unnecessarily.
Basically, for each action in your list, it creates an onTick function that executes that action, then recursively calls DoThings with the remaining actions and delays.
Here, ITimer is just a simple wrapper around DispatcherTimer (but it would work with a SWF Timer as well, or a mock timer for unit testing), and DelayedAction is just a Tuple with int Delay and Action action
public static class TimerEx {
public static void DoThings(this ITimer timer, IEnumerable<DelayedAction> actions) {
timer.DoThings(actions.GetEnumerator());
}
static void DoThings(this ITimer timer, IEnumerator<DelayedAction> actions) {
if (!actions.MoveNext())
return;
var first = actions.Current;
Action onTick = null;
onTick = () => {
timer.IsEnabled = false;
first.Action();
// ReSharper disable AccessToModifiedClosure
timer.Tick -= onTick;
// ReSharper restore AccessToModifiedClosure
onTick = null;
timer.DoThings(actions);
};
timer.Tick += onTick;
timer.Interval = first.Delay;
timer.IsEnabled = true;
}
}
If you don't want to delve into F# or reference Rx or use .Net 4.5 this is a simple viable solution.
Here's an example of how to test it:
[TestClass]
public sealed class TimerExTest {
[TestMethod]
public void Delayed_actions_should_be_scheduled_correctly() {
var timer = new MockTimer();
var i = 0;
var action = new DelayedAction(0, () => ++i);
timer.DoThings(new[] {action, action});
Assert.AreEqual(0, i);
timer.OnTick();
Assert.AreEqual(1, i);
timer.OnTick();
Assert.AreEqual(2, i);
timer.OnTick();
Assert.AreEqual(2, i);
}
}
And here's the other classes to make it compile:
public interface ITimer {
bool IsEnabled { set; }
double Interval { set; }
event Action Tick;
}
public sealed class Timer : ITimer {
readonly DispatcherTimer _timer;
public Timer() {
_timer = new DispatcherTimer();
_timer.Tick += (sender, e) => OnTick();
}
public double Interval {
set { _timer.Interval = TimeSpan.FromMilliseconds(value); }
}
public event Action Tick;
public bool IsEnabled {
set { _timer.IsEnabled = value; }
}
void OnTick() {
var handler = Tick;
if (handler != null) {
handler();
}
}
}
public sealed class MockTimer : ITimer {
public event Action Tick;
public bool IsEnabled { private get; set; }
public double Interval { set { } }
public void OnTick() {
if (IsEnabled) {
var handler = Tick;
if (handler != null) {
handler();
}
}
}
}
public sealed class DelayedAction {
readonly Action _action;
readonly int _delay;
public DelayedAction(int delay, Action action) {
_delay = delay;
_action = action;
}
public Action Action {
get { return _action; }
}
public int Delay {
get { return _delay; }
}
}
If you can use the C# 4.5 to do it, go with Firoso post: it's the best way accomplish that in C#, exactly what Async was built for.
However, if you can't, there might be some ways to do it. I'd do a "simple" manager to do it:
public partial class Form1 : Form
{
private TimedEventsManager _timedEventsManager;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
_timedEventsManager
= new TimedEventsManager(this,
new TimedEvent(1000, () => textBox1.Text += "First\n"),
new TimedEvent(5000, () => textBox1.Text += "Second\n"),
new TimedEvent(2000, () => textBox1.Text += "Third\n")
);
}
private void button1_Click(object sender, EventArgs e)
{
_timedEventsManager.Start();
}
}
public class TimedEvent
{
public int Interval { get; set; }
public Action Action { get; set; }
public TimedEvent(int interval, Action func)
{
Interval = interval;
Action = func;
}
}
public class TimedEventsManager
{
private readonly Control _control;
private readonly Action _chain;
public TimedEventsManager(Control control, params TimedEvent[] timedEvents)
{
_control = control;
Action current = null;
// Create a method chain, beginning by the last and attaching it
// the previous.
for (var i = timedEvents.Length - 1; i >= 0; i--)
{
var i1 = i;
var next = current;
current = () =>
{
Thread.Sleep(timedEvents[i1].Interval);
// MUST run it on the UI thread!
_control.Invoke(new Action(() => timedEvents[i1].Action()));
if (next != null) next();
};
}
_chain = current;
}
public void Start()
{
new Thread(new ThreadStart(_chain)).Start();
}
}
Beware that this example is Winforms specific (uses Control.Invoke()). You will need a slightly different version for WPF, which uses the thread dispatcher to achieve the same thing. (if my memory doesn't fail me, you also can use Control.Dispatcher.Invoke(), but keep in mind that it is a different control)