I have a delegate method to run a heavy process in my app (I must use MS Framework 3.5):
private delegate void delRunJob(string strBox, string strJob);
Execution:
private void run()
{
string strBox = "G4P";
string strJob = "Test";
delRunJob delegateRunJob = new delRunJob(runJobThread);
delegateRunJob.Invoke(strBox, strJob);
}
In some part of the method runJobThread
I call to an external program (SAP - Remote Function Calls) to retrieve data. The execution of that line can take 1-30 mins.
private void runJobThread(string strBox, string strJob)
{
// CODE ...
sapLocFunction.Call(); // When this line is running I cannot cancel the process
// CODE ...
}
I want to allow the user cancel whole process.
How can achieve this? I tried some methods; but I fall in the same point; when this specific line is running I cannot stop the process.
Instead of using the delegate mechanism you have to study the async and await mechanism. When you understand this mechanism you can move to cancellationtoken.
An example doing both things can be found here :
http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx
Well; I find out a complicated, but effective, way to solve my problem:
a.) I created a "Helper application" to show a notification icon when the process is running (To ensure to don't interfere with the normal execution of the main app):
private void callHelper(bool blnClose = false)
{
if (blnClose)
fw.processKill("SDM Helper");
else
Process.Start(fw.appGetPath + "SDM Helper.exe");
}
b.) I created a Thread that call only the heavy process line.
c.) While the Thread is alive I check for external file named "cancel" (The "Helper application" do that; when the user click an option to cancel the process the Helper create the file).
d.) If exists the file; dispose all objects and break the while cycle.
e.) The method sapLocFunction.Call() will raise an exception but I expect errors.
private void runJobThread(string strBox, string strJob)
{
// CODE ...
Thread thrSapCall = new Thread(() =>
{
try { sapLocFunction.Call(); }
catch { /* Do nothing */ }
});
thrSapCall.Start();
while (thrSapCall.IsAlive)
{
Thread.Sleep(1000);
try
{
if (fw.fileExists(fw.appGetPath + "\\cancel"))
{
sapLocFunction = null;
sapLocTable = null;
sapConn.Logoff();
sapConn = null;
canceled = true;
break;
}
}
finally { /* Do nothing */ }
}
thrSapCall = null;
// CODE ...
}
Works like a charm!
I think you would have to resort to the method described here. Read the post to see why this is a long way from ideal.
Perhaps this might work...
private void runJobThread(string strBox, string strJob, CancellationToken token)
{
Thread t = Thread.CurrentThread;
using (token.Register(t.Abort))
{
// CODE ...
sapLocFunction.Call(); // When this line is running I cannot cancel the process
// CODE ...
}
}
A bit of dnspy exposes a cancel method on nco3.0.
private readonly static Type RfcConnection = typeof(RfcSessionManager).Assembly.GetType("SAP.Middleware.Connector.RfcConnection");
private readonly static Func<RfcDestination, object> GetConnection = typeof(RfcSessionManager).GetMethod("GetConnection", BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(typeof(Func<RfcDestination, object>)) as Func<RfcDestination, object>;
private readonly static MethodInfo Cancel = RfcConnection.GetMethod("Cancel", BindingFlags.Instance | BindingFlags.NonPublic);
object connection = null;
var completed = true;
using (var task = Task.Run(() => { connection = GetConnection(destination); rfcFunction.Invoke(destination); }))
{
try
{
completed = task.Wait(TimeSpan.FromSeconds(invokeTimeout));
if (!completed)
Cancel.Invoke(connection, null);
task.Wait();
}
catch(AggregateException e)
{
if (e.InnerException is RfcCommunicationCanceledException && !completed)
throw new TimeoutException($"SAP FM {functionName} on {destination} did not respond in {timeout} seconds.");
throw;
}
}
Related
Here's a demo of code that is calling an "old library" which if successful, returns nothing, and if error, throws an exception.
public void ExampleCallOldCode()
{
List<string> users = new List<string>() {"user123", "user456"};
foreach (string userId in users)
{
try
{
Console.WriteLine($"Processing {userId} started");
DoSomethingSynchronously(userId);
Console.WriteLine($"Processing {userId} completed");
}
catch (Exception e)
{
Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
}
}
}
public void DoSomethingSynchronously(string userId)
{
if (userId.Contains("123"))
Console.WriteLine($"Doing something with {userId}");
else
throw new Exception("UserId needs to contain 123.");
}
We are now upgrading/integrating to a "new library" which performs the work asynchronously (which uses batching/queuing logic behind the scenese) and uses callbacks to notify of success or failure.
FYI: The "old library" is a wrapper that send emails. The new library is Segment's Analytics.Net package
I don't want to change too much of the old code (it is used in MANY places).
How do I wait synchronously for the new library to complete and invoke the callback function?
e.g. Should I use AutoResetEvents and call WaitOne?
Or are there better options?
How do I handle errors?
Would I create a wrapper and throw an exception in the failure callback function?
public void ExampleNewCode()
{
// Segment initialization and payload setup
string writeKey = "PqRStUv1WxYzraGHijkA1Pz0AbcDE12F";
Config configWithBatchingOff = new Config().SetAsync(false);
Segment.Analytics.Initialize(writeKey, configWithBatchingOff);
Context context = new Context() {{"appName", "MyApp"}};
Options optionsContext = new Options().SetContext(context);
Properties properties = new Properties() {{"my_first_prop", "foo bar"}};
// setup callback handlers
Analytics.Client.Failed += FailureHandler;
Analytics.Client.Succeeded += SuccessHandler;
Logger.Handlers += LogHandler;
// ****
// the old code structure I want to preserve. HOW DO I MAKE THIS WORK ???
// ****
List<string> users = new List<string>() { "user123", "user456" };
foreach (string userId in users)
{
try
{
Console.WriteLine($"Processing {userId} started");
Analytics.Client.Track(userId, "Test Fired", properties, optionsContext);
Console.WriteLine($"Processing {userId} completed");
}
catch (Exception e)
{
Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
}
}
}
public void FailureHandler(BaseAction action, System.Exception e)
{
Console.WriteLine($"FailureHandler called for userid={action.UserId}");
}
public void SuccessHandler(BaseAction action)
{
Console.WriteLine($"SuccessHandler called for userid={action.UserId}");
}
If I understand your question correctly then you can solve this problem by using TaskCompletionSource.
For the sake of simplicity I've created a dummy class which might raise an OnSuccess or an OnFailure event depending on the provided parameter:
public class Dummy
{
public event EventHandler OnFailure;
public event EventHandler OnSuccess;
public void DoWork(int i)
{
if (i % 2 == 0) OnFailure?.Invoke(this, null);
else OnSuccess?.Invoke(this, null);
}
}
On the consumer side you can do the following:
private static TaskCompletionSource<object> signalling = new TaskCompletionSource<object>();
public static async Task Main(string[] args)
{
Console.WriteLine("Calling new code");
var dummy = new Dummy();
dummy.OnSuccess += Dummy_OnSuccess;
dummy.OnFailure += Dummy_OnFailure;
dummy.DoWork(2);
try
{
await signalling.Task;
Console.WriteLine("New code has finished");
}
catch (Exception)
{
Console.WriteLine("New code has failed");
}
Console.WriteLine("Calling old code");
}
private static void Dummy_OnFailure(object sender, EventArgs e)
{
Thread.Sleep(1000);
signalling.TrySetException(new Exception("Operation failed"));
}
private static void Dummy_OnSuccess(object sender, EventArgs e)
{
Thread.Sleep(1000);
signalling.TrySetResult(null);
}
Whenever the OnFailure event is emitted then call the TrySetException on the TaskCompletionSource to indicate that the operation has been finished without luck
Whenever the OnSuccess event is emitted the we call the TrySetResult on the TaskCompletionSource to indicate that the operation has been finished with luck
We are await-ing the TaskCompletionSource that's why we can be sure that either the requested operation succeeded or failed after the line of await
Regarding using the segment library, the way you get to know if an action (Identify, Group, Track, Alias, Page or Screen) succeeds or fails when being sent to Segment Server is setting up callback handlers like you did (Failed, Succeeded) and also the Logger Handler.
As your example above, both Succeeded and Failed callbacks receive by parameter the BaseAction which allows you to have information regarding the Action that succeeded or failed.
As you have mentioned, Analytics.NET library sends it Actions asynchronously enqueuing them and when the queue is full then it proceeds to flush those actions and send them in a batch to the segment server. So if you want to send automatically to Segment's server once you invoke any of the Actions, you should call Analytics.Client.Flush() so as to get inmediate feedback on the Succeeded or Failed Callback and the Loggers handler. In your code it will be:
public void ExampleNewCode()
{
// Segment initialization and payload setup
string writeKey = "PqRStUv1WxYzraGHijkA1Pz0AbcDE12F";
Config configWithBatchingOff = new Config().SetAsync(false);
Segment.Analytics.Initialize(writeKey, configWithBatchingOff);
Context context = new Context() {{"appName", "MyApp"}};
Options optionsContext = new Options().SetContext(context);
Properties properties = new Properties() {{"my_first_prop", "foo bar"}};
// setup callback handlers
Analytics.Client.Failed += FailureHandler;
Analytics.Client.Succeeded += SuccessHandler;
Logger.Handlers += LogHandler;
List<string> users = new List<string>() { "user123", "user456" };
foreach (string userId in users)
{
try
{
Console.WriteLine($"Processing {userId} started");
Analytics.Client.Track(userId, "Test Fired", properties, optionsContext);
Analytics.Client.Flush();
Console.WriteLine($"Processing {userId} completed");
}
catch (Exception e)
{
Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
}
}
}
public void FailureHandler(BaseAction action, System.Exception e)
{
Console.WriteLine($"FailureHandler called for userid={action.UserId}");
}
public void SuccessHandler(BaseAction action)
{
Console.WriteLine($"SuccessHandler called for userid={action.UserId}");
}
In my UWP App I need to continuously send data to a UWP app from a WinForms (Win32) component and vice versa. However, I have a weird bug in my WinForms component. Sometimes, upon launching the WinForm, I get a System.InvalidOperationException when calling await connection.SendMessageAsync(message) saying: A method was called at an unexpected time Other times, it works perfectly.
My Code:
private async void SendToUWPVoidAsync(object content)
{
ValueSet message = new ValueSet();
if (content != "request") message.Add("content", content);
else message.Add(content as string, "");
#region SendToUWP
// if connection isn't inited
if (connection == null)
{
// init
connection = new AppServiceConnection();
connection.PackageFamilyName = Package.Current.Id.FamilyName;
connection.AppServiceName = "NotifyIconsUWP";
connection.ServiceClosed += Connection_ServiceClosed;
// attempt connection
AppServiceConnectionStatus connectionStatus = await connection.OpenAsync();
}
AppServiceResponse serviceResponse = await connection.SendMessageAsync(message);
// get response
if (serviceResponse.Message.ContainsKey("content"))
{
object newMessage = null;
serviceResponse.Message.TryGetValue("content", out newMessage);
// if message is an int[]
if (newMessage is int[])
{
// init field vars
int indexInArray = 0;
foreach (int trueorfalse in (int[])newMessage)
{
// set bool state based on index
switch (indexInArray)
{
case 0:
notifyIcon1.Visible = Convert.ToBoolean(trueorfalse);
break;
case 1:
notifyIcon2.Visible = Convert.ToBoolean(trueorfalse);
break;
case 2:
notifyIcon3.Visible = Convert.ToBoolean(trueorfalse);
break;
default:
break;
}
indexInArray++;
}
}
}
#endregion
}
The method is called like this:
private void TCheckLockedKeys_Tick(object sender, EventArgs e)
{
...
if (statusesChanged)
{
// update all bools
bool1 = ...;
bool2 = ...;
bool3 = ...;
// build int[] from bool values
int[] statuses = new int[] { Convert.ToInt32(bool1), Convert.ToInt32(bool2), Convert.ToInt32(bool3) };
// update UWP sibling
SendToUWPVoidAsync(statuses);
}
// ask for new settings
SendToUWPVoidAsync("request");
}
TCheckLockedKeys_Tick.Interval is set to 250 milliseconds.
Is there any way to prevent or to correctly handle this exception without the WinForm Component exiting but still establishing the vital communication path?
Any ideas?
Thanks
Okay, I have found a solution. One might actually call it a workaround.
In my WinForm, I changed the code as follows:
AppServiceResponse serviceResponse = await connection.SendMessageAsync(message);
to:
AppServiceResponse serviceResponse = null;
try
{
// send message
serviceResponse = await connection.SendMessageAsync(message);
}
catch (Exception)
{
// exit
capsLockStatusNI.Visible = false;
numLockStatusNI.Visible = false;
scrollLockStatusNI.Visible = false;
Application.Exit();
}
I have also changed code in my App.xaml.cs file:
private async void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
if (this.appServiceDeferral != null)
{
// Complete the service deferral.
this.appServiceDeferral.Complete();
}
}
to:
private async void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
if (reason == BackgroundTaskCancellationReason.SystemPolicy)
{
// WinForm called Application.Exit()
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
}
if (this.appServiceDeferral != null)
{
// Complete the service deferral.
this.appServiceDeferral.Complete();
}
}
I know all I'm doing is, technically, relaunching the Form till it succeeds which is not entirely the correct way of solving it. But, it works.
Some advice based on
Reference Async/Await - Best Practices in Asynchronous Programming
Avoid using async void except with event handlers. Prefer async Task methods over async void methods.
async void methods a fire an forget, which can cause the issues being encountered as exceptions are not being thrown in the correct context.
Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started.
Given that the method is being called within an event handler. then refactor the method to use an async Task
private async Task SendToUWPVoidAsync(object content) {
//...
}
and update the event handler to be async
private async void TCheckLockedKeys_Tick(object sender, EventArgs e) {
try {
//...
if (statusesChanged) {
// update all bools
bool1 = ...;
bool2 = ...;
bool3 = ...;
// build int[] from bool values
int[] statuses = new int[] { Convert.ToInt32(bool1), Convert.ToInt32(bool2), Convert.ToInt32(bool3) };
// update UWP sibling
await SendToUWPVoidAsync(statuses);
}
// ask for new settings
await SendToUWPVoidAsync("request");
}catch {
//...handle error appropriately
}
}
which should also allow for any exception to be caught as shown in the example above.
I have a problem with thread, I want to create n thread and write a log (with method write, already implemented)
This is an unit test, when I run it, it works nice, but an exception appears :
System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. This can happen if the test(s) started a thread but did not stop it. Make sure that all the threads started by the test(s) are stopped before completion.
So, I tried to use ThreadC.Suspend() and error disappears, but mehod Suspend is obsolete..
How can I fix it?
public void TestMethod1()
{
try
{
LogTest logTest = new LogTest(new FileLog());
logTest.PerformanceTest();
logTest = new LogTest(new CLogApi());
logTest.PerformanceTest();
logTest = new LogTest(new EmptyLog());
logTest.PerformanceTest();
}
catch (Exception)
{
Assert.IsTrue(false);
}
}
public class LogTest
{
private readonly Log log;
private int numberOfIterations = 5;
public LogTest(Log log)
{
this.log = log;
}
public void PerformanceTest()
{
for (int i = 0; i < this.numberOfIterations; i++)
{
try
{
Thread threadC = Thread.CurrentThread;
threadC = new Thread(this.ThreadProc);
threadC.Name = i.ToString();
threadC.Start();
// threadC.IsBackground = true;
}
catch (Exception)
{
Assert.IsTrue(false);
}
}
}
private void ThreadProc()
{
try
{
this.log.Write(" Thread : " + Thread.CurrentThread.Name.ToString());
this.log.Write(" Thread : " + Thread.CurrentThread.Name.ToString());
this.log.Write(" Thread : " + Thread.CurrentThread.Name.ToString());
this.log.Write(" Thread : " + Thread.CurrentThread.Name.ToString());
}
catch (Exception)
{
Assert.IsTrue(false);
}
}
}
1: You should use "Assert.Fail()" instead Assert.IsTrue(false);
2: Read the Microsoft documentation if you use an obsolete method. They write what you can use instead."Thread.Suspend has been deprecated. Please use other classes in System.Threading, such as Monitor, Mutex, Event, and Semaphore, to synchronize Threads or protect resources."
3: If i understand you correctly you want to kill all running threads or wait for them. You can use "Thread.Join()" https://msdn.microsoft.com/de-de/library/95hbf2ta(v=vs.110).aspx
You can store all threads in an Array or list an join all threads at the end.
4: Instead using threads you can use the async pattern and wait for all Tasks with Task.WaitAll(tasks) https://msdn.microsoft.com/en-us/library/dd270695(v=vs.110).aspx
I have a series of code blocks that are taking too long. I don't need any finesse when it fails. In fact, I want to throw an exception when these blocks take too long, and just fall out through our standard error handling. I would prefer to NOT create methods out of each block (which are the only suggestions I've seen so far), as it would require a major rewrite of the code base.
Here's what I would LIKE to create, if possible.
public void MyMethod( ... )
{
...
using (MyTimeoutObject mto = new MyTimeoutObject(new TimeSpan(0,0,30)))
{
// Everything in here must complete within the timespan
// or mto will throw an exception. When the using block
// disposes of mto, then the timer is disabled and
// disaster is averted.
}
...
}
I've created a simple object to do this using the Timer class. (NOTE for those that like to copy/paste: THIS CODE DOES NOT WORK!!)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
public class MyTimeoutObject : IDisposable
{
private Timer timer = null;
public MyTimeoutObject (TimeSpan ts)
{
timer = new Timer();
timer.Elapsed += timer_Elapsed;
timer.Interval = ts.TotalMilliseconds;
timer.Start();
}
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
throw new TimeoutException("A code block has timed out.");
}
public void Dispose()
{
if (timer != null)
{
timer.Stop();
}
}
}
It does not work because the System.Timers.Timer class captures, absorbs and ignores any exceptions thrown within, which -- as I've discovered -- defeats my design. Any other way of creating this class/functionality without a total redesign?
This seemed so simple two hours ago, but is causing me much headache.
OK, I've spent some time on this one and I think I have a solution that will work for you without having to change your code all that much.
The following is how you would use the Timebox class that I created.
public void MyMethod( ... ) {
// some stuff
// instead of this
// using(...){ /* your code here */ }
// you can use this
var timebox = new Timebox(TimeSpan.FromSeconds(1));
timebox.Execute(() =>
{
/* your code here */
});
// some more stuff
}
Here's how Timebox works.
A Timebox object is created with a given Timespan
When Execute is called, the Timebox creates a child AppDomain to hold a TimeboxRuntime object reference, and returns a proxy to it
The TimeboxRuntime object in the child AppDomain takes an Action as input to execute within the child domain
Timebox then creates a task to call the TimeboxRuntime proxy
The task is started (and the action execution starts), and the "main" thread waits for for as long as the given TimeSpan
After the given TimeSpan (or when the task completes), the child AppDomain is unloaded whether the Action was completed or not.
A TimeoutException is thrown if action times out, otherwise if action throws an exception, it is caught by the child AppDomain and returned for the calling AppDomain to throw
A downside is that your program will need elevated enough permissions to create an AppDomain.
Here is a sample program which demonstrates how it works (I believe you can copy-paste this, if you include the correct usings). I also created this gist if you are interested.
public class Program
{
public static void Main()
{
try
{
var timebox = new Timebox(TimeSpan.FromSeconds(1));
timebox.Execute(() =>
{
// do your thing
for (var i = 0; i < 1000; i++)
{
Console.WriteLine(i);
}
});
Console.WriteLine("Didn't Time Out");
}
catch (TimeoutException e)
{
Console.WriteLine("Timed Out");
// handle it
}
catch(Exception e)
{
Console.WriteLine("Another exception was thrown in your timeboxed function");
// handle it
}
Console.WriteLine("Program Finished");
Console.ReadLine();
}
}
public class Timebox
{
private readonly TimeSpan _ts;
public Timebox(TimeSpan ts)
{
_ts = ts;
}
public void Execute(Action func)
{
AppDomain childDomain = null;
try
{
// Construct and initialize settings for a second AppDomain. Perhaps some of
// this is unnecessary but perhaps not.
var domainSetup = new AppDomainSetup()
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
LoaderOptimization = LoaderOptimization.MultiDomainHost
};
// Create the child AppDomain
childDomain = AppDomain.CreateDomain("Timebox Domain", null, domainSetup);
// Create an instance of the timebox runtime child AppDomain
var timeboxRuntime = (ITimeboxRuntime)childDomain.CreateInstanceAndUnwrap(
typeof(TimeboxRuntime).Assembly.FullName, typeof(TimeboxRuntime).FullName);
// Start the runtime, by passing it the function we're timboxing
Exception ex = null;
var timeoutOccurred = true;
var task = new Task(() =>
{
ex = timeboxRuntime.Run(func);
timeoutOccurred = false;
});
// start task, and wait for the alloted timespan. If the method doesn't finish
// by then, then we kill the childDomain and throw a TimeoutException
task.Start();
task.Wait(_ts);
// if the timeout occurred then we throw the exception for the caller to handle.
if(timeoutOccurred)
{
throw new TimeoutException("The child domain timed out");
}
// If no timeout occurred, then throw whatever exception was thrown
// by our child AppDomain, so that calling code "sees" the exception
// thrown by the code that it passes in.
if(ex != null)
{
throw ex;
}
}
finally
{
// kill the child domain whether or not the function has completed
if(childDomain != null) AppDomain.Unload(childDomain);
}
}
// don't strictly need this, but I prefer having an interface point to the proxy
private interface ITimeboxRuntime
{
Exception Run(Action action);
}
// Need to derive from MarshalByRefObject... proxy is returned across AppDomain boundary.
private class TimeboxRuntime : MarshalByRefObject, ITimeboxRuntime
{
public Exception Run(Action action)
{
try
{
// Nike: just do it!
action();
}
catch(Exception e)
{
// return the exception to be thrown in the calling AppDomain
return e;
}
return null;
}
}
}
EDIT:
The reason I went with an AppDomain instead of Threads or Tasks only, is because there is no bullet proof way for terminating Threads or Tasks for arbitrary code [1][2][3]. An AppDomain, for your requirements, seemed like the best approach to me.
Here's an async implementation of timeouts:
...
private readonly semaphore = new SemaphoreSlim(1,1);
...
// total time allowed here is 100ms
var tokenSource = new CancellationTokenSource(100);
try{
await WorkMethod(parameters, tokenSource.Token); // work
} catch (OperationCancelledException ocx){
// gracefully handle cancellations:
label.Text = "Operation timed out";
}
...
public async Task WorkMethod(object prm, CancellationToken ct){
try{
await sem.WaitAsync(ct); // equivalent to lock(object){...}
// synchronized work,
// call tokenSource.Token.ThrowIfCancellationRequested() or
// check tokenSource.IsCancellationRequested in long-running blocks
// and pass ct to other tasks, such as async HTTP or stream operations
} finally {
sem.Release();
}
}
NOT that I advise it, but you could pass the tokenSource instead of its Token into WorkMethod and periodically do tokenSource.CancelAfter(200) to add more time if you're certain you're not at a spot that can be dead-locked (waiting on an HTTP call) but I think that would be an esoteric approach to multithreading.
Instead your threads should be as fast as possible (minimum IO) and one thread can serialize the resources (producer) while others process a queue (consumers) if you need to deal with IO multithreading (say file compression, downloads etc) and avoid deadlock possibility altogether.
I really liked the visual idea of a using statement. However, that is not a viable solution. Why? Well, a sub-thread (the object/thread/timer within the using statement) cannot disrupt the main thread and inject an exception, thus causing it to stop what it was doing and jump to the nearest try/catch. That's what it all boils down to. The more I sat and worked with this, the more that came to light.
In short, it can't be done the way I wanted to do it.
However, I've taken Pieter's approach and mangled my code a bit. It does introduce some readability issues, but I've tried to mitigate them with comments and such.
public void MyMethod( ... )
{
...
// Placeholder for thread to kill if the action times out.
Thread threadToKill = null;
Action wrappedAction = () =>
{
// Take note of the action's thread. We may need to kill it later.
threadToKill = Thread.CurrentThread;
...
/* DO STUFF HERE */
...
};
// Now, execute the action. We'll deal with the action timeouts below.
IAsyncResult result = wrappedAction.BeginInvoke(null, null);
// Set the timeout to 10 minutes.
if (result.AsyncWaitHandle.WaitOne(10 * 60 * 1000))
{
// Everything was successful. Just clean up the invoke and get out.
wrappedAction.EndInvoke(result);
}
else
{
// We have timed out. We need to abort the thread!!
// Don't let it continue to try to do work. Something may be stuck.
threadToKill.Abort();
throw new TimeoutException("This code block timed out");
}
...
}
Since I'm doing this in three or four places per major section, this does get harder to read over. However, it works quite well.
While keeping in mind that:
I am using a blocking queue that waits for ever until something is added to it
I might get a FileSystemWatcher event twice
The updated code:
{
FileProcessingManager processingManager = new FileProcessingManager();
processingManager.RegisterProcessor(new ExcelFileProcessor());
processingManager.RegisterProcessor(new PdfFileProcessor());
processingManager.Completed += new ProcessingCompletedHandler(ProcessingCompletedHandler);
processingManager.Completed += new ProcessingCompletedHandler(LogFileStatus);
while (true)
{
try
{
var jobData = (JobData)fileMonitor.FileQueue.Dequeue();
if (jobData == null)
break;
_pool.WaitOne();
Application.Log(String.Format("{0}:{1}", DateTime.Now.ToString(CultureInfo.InvariantCulture), "Thread launched"));
Task.Factory.StartNew(() => processingManager.Process(jobData));
}
catch (Exception e)
{
Application.Log(String.Format("{0}:{1}", DateTime.Now.ToString(CultureInfo.InvariantCulture), e.Message));
}
}
}
What are are you suggestions on making the code multi-threaded while taking into consideration the possibility that two identical string paths may be added into the blocking queue? I have left the possibility that this might happen and in this case.. the file would be processed twice, the thing is that sometimes I get it twice, sometimes not, it is really awkward, if you have suggestions on this, please tell.
The null checking is for exiting the loop, I intentionally add a null from outside the threaded loop to determine it to stop.
For multi-threading this... I would probably add a "Completed" event to your FileProcessingManager and register for it. One argument of that event will be the "bool" return value you currently have. Then in that event handler, I would do the checking of the bool and re-queueing of the file. Note that you will have to keep a reference to the FileMonitorManager. So, I would have this ThreadProc method be in a class where you keep the FileMonitorManager and FileProcessingManager instances in a property.
To deduplicate, in ThreadProc, I would create a List outside of the while loop. Then inside the while loop, before you process a file, lock that list, check to see if the string is already in there, if not, add the string to the list and process the file, if it is, then skip processing.
Obviously, this is based on little information surrounding your method but my 2 cents anyway.
Rough code, from Notepad:
private static FileMonitorManager fileMon = null;
private static FileProcessingManager processingManager = new FileProcessingManager();
private static void ThreadProc(object param)
{
processingManager.RegisterProcessor(new ExcelFileProcessor());
processingManager.RegisterProcessor(new PdfFileProcessor());
processingManager.Completed += ProcessingCompletedHandler;
var procList = new List<string>();
while (true)
{
try
{
var path = (string)fileMon.FileQueue.Dequeue();
if (path == null)
break;
bool processThis = false;
lock(procList)
{
if(!procList.Contains(path))
{
processThis = true;
procList.Add(path);
}
}
if(processThis)
{
Thread t = new Thread (new ParameterizedThreadStart(processingManager.Process));
t.Start (path);
}
}
catch (System.Exception e)
{
Console.WriteLine(e.Message);
}
}
}
private static void ProcessingCompletedHandler(bool status, string path)
{
if (!status)
{
fileMon.FileQueue.Enqueue(path);
Console.WriteLine("\n\nError on file: " + path);
}
else
Console.WriteLine("\n\nSucces on file: " + path);
}