Task with Timer crashing a program - c#

I've a little program, that parses all the log files created by another program, and locked by it ( so, no way I can edit or delete those files) . The program runs just fine, and I do it starting a new Task every 10 seconds:
System.Timers.Timer aTimer = new System.Timers.Timer();
public Form1()
{
InitializeComponent();
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
aTimer.Interval = 10000;
aTimer.Start();
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
var t = Task<int>.Factory.StartNew(() => convert());
}
the only problem arises when there are too many log files : if a new Task is started before the end of the previous one, the program crashes.
So, any idea on how to solve this behaviour, or better solutions to the problem?

You could use the lock() statement to lock on an object variable. On the other hand, you might run into thread deadlocks if the parsing of the log files consistently takes longer than the timer interval.
In your OnTimedEvent() function, I would check a boolean member variable that skips the parsing if you are already performing a parse. For example:
public class MyTimerClass
{
private bool isParsing;
// Other methods here which initiate the log file parsing.
private void OnTimedEvent(object sender, ElapsedEventArgs e)
{
if (!isParsing)
{
isParsing = true;
ParseLogFiles();
isParsing = false;
}
}
}

The simple solution would be to wait until the previous task is completed.
Write an event that sends a callback when the file is done being parsed.
This is the best I can do with the code provided.

Have you tried to use lock statement inside OnTimeEvent?
http://msdn.microsoft.com/en-us/library/c5kehkcz(v=VS.100).aspx

You could create a static boolean variable called IsRunning and set it to true when you are moving the logs, before you start moving the logs just check if IsRunning is set to true.
private static bool IsRunning = false;
public void MoveLogs()
{
if (!IsRunning)
{
IsRunning = true;
//Copy log files
IsRunning = false;
}
}

In the current accepted answer there is still the possibility of a race condition in a multi-threaded situation. However unlikely in your case because of the interval, another more threading appropriate solution is to use Monitor.TryEnter
public class MyTimerClass
{
private object _syncObject = new object();
// Other methods here which initiate the log file parsing.
private void OnTimedEvent(object sender, ElapsedEventArgs e)
{
if (Monitor.TryEnter(_syncObject) )
{
try
{
ParseLogFiles();
}
finally
{
Monitor.Exit(_syncObject);
}
}
}
}
I believe this is cleaner and gets you in the habit of using the proper thread synchronization mechanism in the framework.

Related

Windows Service constantly looking for files

Sorry if this is an stupid question, but I'm pretty new to Windows Services and want to make sure I understand the proper way to approach this situation.
I have a Windows Service that is meant to watch for files and if those files exist, it processes them. I was looking through an old developer's code and they used Thread.Sleep() if files didn't exist. I understand that this is bad practice and have seen it first-hand that this locks up the service.
Here's a simplified example of my logic:
private Task _processFilesTask;
private CancellationTokenSource _cancellationTokenSource;
public Start()
{
_cancellationTokenSource = new CancellationTokenSource();
_processFilesTask = Task.Run(() => DoWorkAsync(_cancellationTokenSource.Token))
}
public async Task DoWorkAsync(CancellationToken token)
{
while(!token.IsCancellationRequested)
{
ProcessFiles();
//await Task.Delay(10000);
}
}
public Stop()
{
_cancellationTokenSource.Cancel();
_processFilesTask.Wait();
}
private void ProcessFiles()
{
FileInfo xmlFile = new DirectoryInfo(Configs.Xml_Input_Path).GetFiles("*.xml").OrderBy(p => p.CreationTime).FirstOrDefault();
if(xmlFile != null)
{
//read xml
//write contents to db
//move document specified in xml to another file location
//delete xml
}
}
My first question: Is any sort of Delay or Pause even needed? If I don't have any pause then this service will constantly look for files on a remote server. Is this something I have to worry about or is it a pretty light-weight process?
Second question: If it would be better to pause instead of constantly hitting this server, is this a better approach or what would you recommend?
public async Task DoWorkAsync(CancellationToken token)
{
while(!token.IsCancellationRequested)
{
ProcessFiles();
await Task.Delay(TimeSpan.FromMilliseconds(10000), token).ContinueWith(_processFilesTask=> { });
}
}
Thanks for your help!
You can use timer to check desired files and folder. Here is outline.
Add Timer in your service class (one that inherits ServiceBase class)
private System.Timers.Timer myTimer;
Initialize timer in OnStart method.
protected override void OnStart(string[] args)
{
// Set the Interval to 1 seconds (1000 milliseconds).
myTimer = new System.Timers.Timer(1000);
// Hook up the Elapsed event for the timer.
myTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
myTimer.Enabled = true;
}
Define elapsed event handler.
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
//Write your file handling logic here.
//Service will execute this code after every one second interval
//as set in OnStart method.
}

Calling a method after set amount of time and/or aborting thread issues

So I've got an application that employs a filesystemWatcher and triggers an event just fine. The FSW will trigger a bunch of times pretty close together. I want to create a function that triggers say an hour after the last time the FSW was triggered.
I first tried using a backgroundworker: (All code is shortened for clarity)
namespace Devo
{
public partial class Form1 : Form
{
BackgroundWorker bw = new BackgroundWorker();
private void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
if (bw.IsBusy)
{
bw.CancelAsync(); //this is to, in a way, reset the timer for the delayed method.
}
//do a lot of stuff
bw.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();
while(sw.ElapsedMilliseconds < 180000)
{
if (bw.CancellationPending == true)
{
sw.Stop();
sw.Reset();
e.Cancel = true;
return;
}
}
sw.Stop();
sw.Reset();
DelayedMethod();
}
}
}
This didn't work as the second time bw.RunWorkerAsync() was called it was apparently busy, even though the call to bw.CancelAsync().
My next attempt involved a regular thread as I read somewhere on SO (can't find the link now) that one could not "restart" a backgroundWorker as I am trying to do.
The thread attemp is nearly identical but I thought I'd try in since there might be some constraints within the backgroundWorker that is not present in a regular thread. I thought.
namespace Devo
{
public partial class Form1 : Form
{
Thread PWC_counter_thread = new Thread(PWC_Counter);
private void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
if (PWC_counter_thread.IsAlive)
PWC_counter_thread.Abort();
//do a lot of stuff
PWC_counter_thread.Start();
}
static void PWC_Counter()
{
Thread.Sleep(180000);
DelayedMethod();
}
}
}
But this gave me the same error. On the second call to PWC_counter_thread.Start() is was busy.
I'm assuming that a race condition is not present as the second thread waits for, in this example, 3 minutes, and the initial FSW method takes a good full second to execute, therefore I believe that the call to .Abort() and .CancelAsync() both are done before their respective methods are completed.
Now for the questions:
Is it possible to restart a thread in the fashion I am trying? If so, what am I doing wrong?
Should I delay my method call in another way? If so, tips?
EDIT/UPDATE/SOLUTION
I never got starting and stopping a thread to work as I wanted so I found another solution to my situation.
The situation was that I had a second thread that worked as a sort of timer where it would call a method after a set amount of time. My first thread did some work and upon finishing it would start the second thread. If the first thread got fired up again before the timer-thread had finished it was supposed to kill the thread and restart it.
This proved, for me, to be difficult to get the way I wanted. So I instead took another approach towards my wanted end result. Instead of restarting the thread I simply restarted the stopwatch that my second thread was using as a counter. This gave me the result I wanted. It's probably bad practice but it works.
In your BackgroundWorker example you probably have an issue with racing. CancelAsync(), as its name implies, is an asynchronious call, meaning that BackgroundWorker does not stop working immediately and it might still work when try to restart it. To avoid that, you should subscribe to RunWorkerCompleted event and wait for it to fire before calling bw.RunWorkerAsync(); again. For example:
public Form1()
{
bw = new BackgroundWorker();
bw.RunWorkerCompleted += OnCompleted;
}
private BackgroundWorker bw;
private ManualResetEvent completed = new ManualResetEvent(false);
private void OnCompleted(object sender, RunWorkerCompletedEventArgs e)
{
completed.Set();
}
private void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
if (bw.IsBusy)
{
bw.CancelAsync();
completed.WaitOne();
}
//do a lot of stuff
completed.Reset();
bw.RunWorkerAsync();
}
You have multiple issues with your Thread-based example.
You should never call Thread.Abort(). Instead, you should implement a cancellation mechanism, similar to that of BackgroundWorker. Make a bool field (_isCancelled or something) and check it periodically in thread delegate.
You can not reuse a Thread object. You should always create a new one.
You would be best off encapsulating this in a class, and use a System.Threading.Timer to detect the inactivity.
Here's an example I put together. The idea is that you create an InactivityDetector with the appropriate inactivity threshold (an hour in your case) and a callback method that will be called when that period of inactivity is exceeded.
You have to call InactivityDetector.RegisterActivity() whenever activity is detected (e.g. in your case a file creation is detected).
Once the inactivity callback has been issued, it will not be called again until RegisterActivity() has been called again (this prevents multiple callbacks for the same period of extended inactivity).
Your code would pass DelayedMethod for the inactivity Action delegate.
Note that the callback is on a separate thread!
(Also note that I didn't put in any parameter validation, to keep the code shorter.)
using System;
using System.Threading;
namespace ConsoleApp1
{
sealed class Program
{
void test()
{
using (var inactivityDetector = new InactivityDetector(TimeSpan.FromSeconds(2), inactivityDetected))
{
for (int loop = 0; loop < 3; ++loop)
{
Console.WriteLine("Keeping busy once a second for 5 seconds.");
for (int i = 0; i < 5; ++i)
{
Thread.Sleep(1000);
Console.WriteLine("Registering activity");
inactivityDetector.RegisterActivity();
}
Console.WriteLine("Entering 3 second inactivity");
Thread.Sleep(3000);
inactivityDetector.RegisterActivity();
}
}
}
static void inactivityDetected()
{
Console.WriteLine("Inactivity detected.");
}
static void Main(string[] args)
{
new Program().test();
}
}
public sealed class InactivityDetector: IDisposable
{
public InactivityDetector(TimeSpan inactivityThreshold, Action onInactivity)
{
_inactivityThreshold = inactivityThreshold;
_onInactivity = onInactivity;
_timer = new Timer(timerCallback, null, (int)inactivityThreshold.TotalMilliseconds, -1);
}
public void RegisterActivity()
{
_timer.Change(-1, -1);
_timer.Change((int)_inactivityThreshold.TotalMilliseconds, -1);
}
private void timerCallback(object state)
{
_timer.Change(-1, -1);
_onInactivity();
}
public void Dispose()
{
_timer.Dispose();
}
private readonly TimeSpan _inactivityThreshold;
private readonly Action _onInactivity;
private readonly Timer _timer;
}
}

Reading one file at a time

I have the following code to look for new files in the directory:
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = #"C:\temp\Dir1\";
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
and the OnChanged event handler:
public void OnChanged(object source, FileSystemEventArgs e)
{
Thread t = new Thread(readFile);
t.Start(e.FullPath);
}
and the readFile thread method:
public void readFile(){
FSReader vsfr = new FSReader((string)path);
while (vsfr.EndOfStream == false)
{
PrintLine(vsfr.ReadLine());
}
vsfr.Close();
}
So every time a new file is created, it's being read by readFile thread. The problem occurs when the first file is being read and the second file starts to be read by another thread.
What I need is first thread to get terminated when the second thread opens so there's only a single thread running at a time to read a single file. I know that I need to use lock and events to accomplish this.
When I use lock on the readFile method, I get the desired result of a single thread running at once, but I still need a way to tell the previous thread to close, when new thread opens. How can I do this?
Any tips? Thanks.
Sounds pretty easy, just add a lock when you reading files and some simple cancelation
string _path;
Thread
object _lock = new object();
volatile bool _running;
volatile bool _cancel;
public void OnChanged(object source, FileSystemEventArgs e)
{
// waiting
while(_running)
{
_cancel = true;
Thread.Sleep(0);
}
// start new thread
_cancel = false;
_path = e.FullPath;
(new Thread(ReadFile)).Start();
}
public void ReadFile()
{
lock(_lock)
{
_running = true;
using(var reader = new FSReader(_path))
while (!reader.EndOfStream)
{
if(_cancel)
break;
PrintLine(reader.ReadLine());
}
_running = false;
}
}
I assume, you want to have only 1 thread working at a time.
When creating a new thread, you have to check if it is already running and if is, then set cancel and wait for it to finish.
It is possible to keep Thread instance and check IsAlive instead of using _running.
You could use Task instead of Thread. You can use ManualResetEvent to avoid need for a sleep, though in this scenario Sleeps looks harmless to me.
Edit
Let's play with it
volatile bool _cancel;
Mutex _mutext = new Mutex(false);
public void OnChanged(object source, FileSystemEventArgs e)
{
_cancel = true;
_mutex.WaitOne();
// start new thread
_cancel = false;
_mutex.ReleaseMutex();
(new Thread(ReadFile)).Start(e.FullPath);
}
public void ReadFile(string path)
{
_mutex.WaitOne();
using(var reader = new FSReader(path))
while (!reader.EndOfStream)
{
if(_cancel)
break;
PrintLine(reader.ReadLine());
}
_mutex.ReleaseMutex();
}
Now we are using Mutex to ensure, what no new thread will be started, until existing (if any) will finish its work. No need for lock (mutex do all the job).
Edit
Actually, there is a slim but chance, what if OnChanged will be called before thread takes mutex ownership (it's possible in theory), then we are in trouble.
Solution would be to implementing sort of ping-pong waiting between thread and event (two mutexes, or perhaps some other synchronization primitive).
I'd do something along these lines. Obviously add your own locks/synchronisation.
while (vsfr.EndOfStream == false)
{
if (cancelThread)
{
vsfr.Close()
return;
}
PrintLine(vsfr.ReadLine());
}
and
public void OnChanged(object source, FileSystemEventArgs e)
{
Thread t = new Thread(readFile);
//If a thread is running, set the cancellation flag = true
//If you have some class-level reference to the running thread, you can then wait for it to .Join()
//Set cancellation flag back to false
t.Start(e.FullPath);
}

Reliably stop System.Threading.Timer?

Well I've searched a lot for a solution to this. I'm looking for a clean and simple way to prevent the callback method of a System.Threading.Timer from being invoked after I've stopped it.
I can't seem to find any, and this has led me, on occassion, to resort to the dreaded thread-thread.sleep-thread.abort combo.
Can it be done using lock?
An easier solution might to be to set the Timer never to resume; the method Timer.Change can take values for dueTime and period that instruct the timer never to restart:
this.Timer.Change(Timeout.Infinite, Timeout.Infinite);
Whilst changing to use System.Timers.Timer might be a "better" solution, there are always going to be times when that's not practical; just using Timeout.Infinite should suffice.
like Conrad Frix suggested you should use the System.Timers.Timer class instead, like:
private System.Timers.Timer _timer = new System.Timers.Timer();
private volatile bool _requestStop = false;
public constructor()
{
_timer.Interval = 100;
_timer.Elapsed += OnTimerElapsed;
_timer.AutoReset = false;
_timer.Start();
}
private void OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// do work....
if (!_requestStop)
{
_timer.Start();//restart the timer
}
}
private void Stop()
{
_requestStop = true;
_timer.Stop();
}
private void Start()
{
_requestStop = false;
_timer.Start();
}
The MSDN Docs suggest that you use the Dispose(WaitHandle) method to stop the timer + be informed when callbacks will no longer be invoked.
For the System.Threading.Timer one can do the following (Will also protect the callback-method from working on a disposed timer - ObjectDisposedException):
class TimerHelper : IDisposable
{
private System.Threading.Timer _timer;
private readonly object _threadLock = new object();
public event Action<Timer,object> TimerEvent;
public void Start(TimeSpan timerInterval, bool triggerAtStart = false,
object state = null)
{
Stop();
_timer = new System.Threading.Timer(Timer_Elapsed, state,
System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
if (triggerAtStart)
{
_timer.Change(TimeSpan.FromTicks(0), timerInterval);
}
else
{
_timer.Change(timerInterval, timerInterval);
}
}
public void Stop(TimeSpan timeout = TimeSpan.FromMinutes(2))
{
// Wait for timer queue to be emptied, before we continue
// (Timer threads should have left the callback method given)
// - http://woowaabob.blogspot.dk/2010/05/properly-disposing-systemthreadingtimer.html
// - http://blogs.msdn.com/b/danielvl/archive/2011/02/18/disposing-system-threading-timer.aspx
lock (_threadLock)
{
if (_timer != null)
{
ManualResetEvent waitHandle = new ManualResetEvent(false)
if (_timer.Dispose(waitHandle))
{
// Timer has not been disposed by someone else
if (!waitHandle.WaitOne(timeout))
throw new TimeoutException("Timeout waiting for timer to stop");
}
waitHandle.Close(); // Only close if Dispose has completed succesful
_timer = null;
}
}
}
public void Dispose()
{
Stop();
TimerEvent = null;
}
void Timer_Elapsed(object state)
{
// Ensure that we don't have multiple timers active at the same time
// - Also prevents ObjectDisposedException when using Timer-object
// inside this method
// - Maybe consider to use _timer.Change(interval, Timeout.Infinite)
// (AutoReset = false)
if (Monitor.TryEnter(_threadLock))
{
try
{
if (_timer==null)
return;
Action<Timer, object> timerEvent = TimerEvent;
if (timerEvent != null)
{
timerEvent(_timer, state);
}
}
finally
{
Monitor.Exit(_threadLock);
}
}
}
}
This is how one can use it:
void StartTimer()
{
TimerHelper _timerHelper = new TimerHelper();
_timerHelper.TimerEvent += (timer,state) => Timer_Elapsed();
_timerHelper.Start(TimeSpan.FromSeconds(5));
System.Threading.Sleep(TimeSpan.FromSeconds(12));
_timerHelper.Stop();
}
void Timer_Elapsed()
{
// Do what you want to do
}
For what it's worth, we use this pattern quite a bit:
// set up timer
Timer timer = new Timer(...);
...
// stop timer
timer.Dispose();
timer = null;
...
// timer callback
{
if (timer != null)
{
..
}
}
This answer relates to System.Threading.Timer
I've read a lot of nonsense about how to synchronize disposal of System.Threading.Timer all over the net. So that's why I'm posting this in an attempt to rectify the situation somewhat. Feel free to tell me off / call me out if something I'm writing is wrong ;-)
Pitfalls
In my opinion there's these pitfalls:
Timer.Dispose(WaitHandle) can return false. It does so in case it's already been disposed (I had to look at the source code). In that case it won't set the WaitHandle - so don't wait on it!
not handling a WaitHandle timeout. Seriously - what are you waiting for in case you're not interested in a timeout?
Concurrency issue as mentioned here on msdn where an ObjectDisposedException can occur during (not after) disposal.
Timer.Dispose(WaitHandle) does not work properly with -Slim waithandles, or not as one would expect. For example, the following does not work (it blocks forever):
using(var manualResetEventSlim = new ManualResetEventSlim)
{
timer.Dispose(manualResetEventSlim.WaitHandle);
manualResetEventSlim.Wait();
}
Solution
Well the title is a bit "bold" i guess, but below is my attempt to deal with the issue - a wrapper which handles double-disposal, timeouts, and ObjectDisposedException. It does not provide all of the methods on Timer though - but feel free to add them.
internal class Timer
{
private readonly TimeSpan _disposalTimeout;
private readonly System.Threading.Timer _timer;
private bool _disposeEnded;
public Timer(TimeSpan disposalTimeout)
{
_disposalTimeout = disposalTimeout;
_timer = new System.Threading.Timer(HandleTimerElapsed);
}
public event Action Elapsed;
public void TriggerOnceIn(TimeSpan time)
{
try
{
_timer.Change(time, Timeout.InfiniteTimeSpan);
}
catch (ObjectDisposedException)
{
// race condition with Dispose can cause trigger to be called when underlying
// timer is being disposed - and a change will fail in this case.
// see
// https://msdn.microsoft.com/en-us/library/b97tkt95(v=vs.110).aspx#Anchor_2
if (_disposeEnded)
{
// we still want to throw the exception in case someone really tries
// to change the timer after disposal has finished
// of course there's a slight race condition here where we might not
// throw even though disposal is already done.
// since the offending code would most likely already be "failing"
// unreliably i personally can live with increasing the
// "unreliable failure" time-window slightly
throw;
}
}
}
private void HandleTimerElapsed(object state)
{
Elapsed?.Invoke();
}
public void Dispose()
{
var waitHandle = new ManualResetEvent(false));
// returns false on second dispose
if (_timer.Dispose(waitHandle))
{
if (waitHandle.WaitOne(_disposalTimeout))
{
_disposeEnded = true;
waitHandle.Dispose();
}
else
{
// don't dispose the wait handle, because the timer might still use it.
// Disposing it might cause an ObjectDisposedException on
// the timer thread - whereas not disposing it will
// result in the GC cleaning up the resources later
throw new TimeoutException(
"Timeout waiting for timer to stop. (...)");
}
}
}
}
You can't guarantee that your code that supposed to stop the timer will execute before timer event invocation.
For example, suppose on time moment 0 you initialized timer to call event when time moment 5 comes. Then on time moment 3 you decided that you no longer needed the call. And called method you want to write here. Then while method was JIT-ted comes time moment 4 and OS decides that your thread exhaust its time slice and switch. And timer will invoke the event no matter how you try - your code just won't have a chance to run in worst case scenario.
That's why it is safer to provide some logic in the event handler. Maybe some ManualResetEvent that will be Reset as soon as you no longer needed event invocation. So you Dispose the timer, and then set the ManualResetEvent. And in the timer event handler first thing you do is test ManualResetEvent. If it is in reset state - just return immediately. Thus you can effectively guard against undesired execution of some code.
To me, this seems to be the correct way to go:
Just call dispose when you are done with the timer. That will stop the timer and prevent future scheduled calls.
See example below.
class Program
{
static void Main(string[] args)
{
WriteOneEverySecond w = new WriteOneEverySecond();
w.ScheduleInBackground();
Console.ReadKey();
w.StopTimer();
Console.ReadKey();
}
}
class WriteOneEverySecond
{
private Timer myTimer;
public void StopTimer()
{
myTimer.Dispose();
myTimer = null;
}
public void ScheduleInBackground()
{
myTimer = new Timer(RunJob, null, 1000, 1000);
}
public void RunJob(object state)
{
Console.WriteLine("Timer Fired at: " + DateTime.Now);
}
}
Perhaps you should do the opposite. Use system.timers.timer, set the AutoReset to false and only Start it when you want to
You can stop a timer by creating a class like this and calling it from, for example, your callback method:
public class InvalidWaitHandle : WaitHandle
{
public IntPtr Handle
{
get { return InvalidHandle; }
set { throw new InvalidOperationException(); }
}
}
Instantiating timer:
_t = new Timer(DisplayTimerCallback, TBlockTimerDisplay, 0, 1000);
Then inside callback method:
if (_secondsElapsed > 80)
{
_t.Dispose(new InvalidWaitHandle());
}
There is a MSDN link how to achieve stop timer correctly. Use ControlThreadProc() method with HandleElapsed(object sender, ElapsedEventArgs e) event synchronized by syncPoint static class variable. Comment out Thread.Sleep(testRunsFor); on ControlThreadProc() if it is not suitable(probably).
The key is there that using static variable and an atomic operation like Interlocked.CompareExchange on conditional statements.
Link :
Timer.Stop Method

C# event not being handled

I'm learning C# event handling by writing an app that uses the iTunes COM API. I have a method that should run when iTunes stops playing a song, but the method is never getting called when I trigger the event in the app by hitting the "stop/pause" button.
EDIT: Based on dboarman's reply, I deleted the while loop. Now the event does get handled, but only if I restart iTunes prior to running PlayPlaylist(). If I run PlayPlaylist() a second time, the stop event no longer gets fired/handled.
void trayIcon_Click(object sender, EventArgs e)
{
PlayPlaylist();
}
public static void PlayPlaylist()
{
itapp = new iTunesApp();
itapp.OnPlayerStopEvent +=
new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
lastPlaylist = itapp.LibraryPlaylist;
itapp.Play();
}
static void itapp_OnPlayerStopEvent(object iTrack)
{
Debug.WriteLine("Stop Event fired");
//...
}
Updated source in Pastebin here (lines 59-68 are the relevant ones).
Spec: My app is supposed to play the songs in a Genius recommendations playlist from first to last (iTunes by default doesn't play Genius recommendations consecutively). The StopEvent should trigger the next song in the list to play.
Here is the complete code that is in question:
public static void PlayPlaylist()
{
itapp = new iTunesApp();
itapp.OnPlayerStopEvent += new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
lastPlaylistID = itapp.LibraryPlaylist.playlistID;
Debug.WriteLine("Last playlist:");
Debug.WriteLine(lastPlaylistID);
itapp.Play();
while (true)
{
System.Threading.Thread.Sleep(1000);
}
}
I suspect that the while loop is causing the event to never fire because the thread will sleep for a second and because true is, well...always true.
I would put your playlist in into a list. Something like:
static List<myTrack> Tracks;
public static void PlayPlaylist()
{
itapp = new iTunesApp();
itapp.OnPlayerStopEvent += new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
foreach (myTrack track in Tracks)
{
// perform play
}
}
See how that works for you.
When your itapp goes out of scope, be sure to release it with
System.Runtime.InteropServices.Marshal.ReleaseComObject(itapp);
or you'll have to restart iTunes for it to work again. Unregistering the event handlers with -= probably wouldn't hurt either.
If you want the thread to block and wait for the event you can use the ManualResetEvent class.
private ManualResetEvent _resetEvent;
public void PlayPlaylist()
{
_resetEvent = new ManualResetEvent(false);
itapp = new iTunesApp();
itapp.OnPlayerStopEvent += new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
// Block until the resetEvent has been Set() or
// give up waiting after 5 minutes
_resetEvent.WaitOne(1000*5*60);
}
Inside itapp_OnPlayerStopEvent() you must call:
_resetEvent.Set();
To answer your original question, I'm pretty sure the while loop is not giving the thread any time to respond to the stop event, hence you are not seeing it being handled.
I'm wondering if the fact that the event handler doesn't unhook is causing an issue somewhere along the line (i.e. iTunes holds a singular reference to the initial instance of your app). This may solve it? I don't have the iTunes API so I'm flying a little blind; apologize if it's a waste of time.
private bool stopIt;
private void trayIcon_Click(object sender, EventArgs e)
{
if (!stopIt)
{
PlayPlaylist();
stopIt = true;
}
else
{
StopPlaylist();
stopIt = false;
}
}
public static void PlayPlaylist()
{
itapp = new iTunesApp();
itapp.OnPlayerStopEvent +=
new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
lastPlaylist = itapp.LibraryPlaylist;
itapp.Play();
}
public static void StopPlaylist()
{
itapp.Stop(); // don't know if this is the right method name
// unhook the event so the reference object can free if necessary.
itapp.OnPlayerStopEvent -=
new _IiTunesEvents_OnPlayerStopEventEventHandler(itapp_OnPlayerStopEvent);
}
private static void itapp_OnPlayerStopEvent(object iTrack)
{
Debug.WriteLine("Stop Event fired");
// ...
}

Categories

Resources