I am trying to make a plugin type system. I have made something along these lines in the past where all plugins execute in the main thread, which leads to problems if the plugins are taking a long time. So I thought I'd execute the appropriate methods in each plugin using Tasks.
I have a main program, which loads each plugin using Assembly.LoadFile, and then reacts to commands a user types. If one of these commands is handled by a plugin (the plugins report what commands they handle, the main program asks when it loads them), my program will start a method in the plugin in its own Task.
Task t = Task.Factory.StartNew(() => Plugin.ProcessCommand(cmd, Params, Context));
Each plugin also implements an event used when it has data to send to the main program for output. The main program attaches a handler to this event when it loads each plugin.
The plugin's ProcessCommand method does whatever work is needed, triggers the OnOutput event, and then ends.
This is a very simple plugin:
public override void ProcessCommand(PluginCommand Command, PluginParameters Parameters, PluginContext Context)
{
OnOutputGenerated(this,"Hello from Plugin A");
}
This worked fine with the first plugin I made. So I created another, using the exact same code, just changing "Hello from Plugin A" to "Hello from Plugin B."
Plugin A always works. If I issue the appropriate command in the main program, it runs and says Hello from Plugin A. Great.
The problem is: Plugin B executes maybe one in every 30 attempts. I've discovered, however, that if call the plugin in the following way, it works every time:
Task t = Task.Factory.StartNew(() => Plugin.ProcessCommand(cmd, Params, Context));
t.Wait(100);
Is there a technical reason why this might help? I've read through pretty much all of http://www.albahari.com/threading/ trying to figure things out, but I've not had any luck.
It's worth noting that I've also done this with threads, with the same problem.
Thread t = new Thread(() => Plugin.ProcessCommand(cmd, Params, Context));
t.Start();
Adding:
t.Join(100);
"fixed" it.
Updated
I've simplified everything. I've made a new project, which strips out all the code unrelated to the bugs.
foreach (string File in Directory.GetFiles(PluginDir, "*.dll")) {
try {
IPlugin Plugin = PluginManager.LoadPlugin(File);
Plugin.OnOutputGenerated += new PluginOutputEvent(Plugin_OnOutputGenerated);
} catch (Exception ex) {
}
}
// main loop
string Line = Console.ReadLine();
foreach (IPlugin Plugin in PluginManager.LoadedPlugins) {
foreach (PluginCommand cmd in Plugin.GetCommands()) {
if (cmd.Command.Equals(Line, StringComparison.InvariantCultureIgnoreCase)) {
PluginParameters Params = cmd.TryParseParameters(ParamString);
Task t = Task.Factory.StartNew(() => Plugin.ProcessCommand(cmd, Params, Context));
}
}
}
// output handler
static void Plugin_OnOutputGenerated(IPlugin Plugin, string OutputString) {
Console.WriteLine("Output: " + OutputString);
}
The main issue has changed. Previously, one of the plugins didn't work most of the time. Imagine two plugins.
Plugin A
* Has one command: CommandA
* Command triggers OnOutputGenerated event with the string "Hello from Plugin A"
Plugin B
* Has one command: CommandB
* Command triggers OnOutputGenerated event with the string "Hello from Plugin B"
If I run this new project I've made, and issue the command "CommandA", it will return "Hello from Plugin B". It continues doing this until I actually issue "CommandB". Once I've done that, it prints "Hello from Plugin B" (as it should). If I then issue "CommandA" again, it returns "Hello from Plugin A" (as it should have originally).
If I add
t.Wait(100);
it's fixed. It still seems to be related to the Task somehow, but I'm at a loss to explain how. It would appear that my logic otherwise is fine. I can't see how it would execute Plugin B when it should execute Plugin A, or vice-versa.
It sounds like without the Wait or Join, your main program simply exits before the requested Task code has a chance to run. If the Task logic used to be inline in the main thread, that would have implied the main thread would wait while the code executed. Now you have moved it to a separate thread, you have to add an explicit wait to allow every Task you start up to complete (maybe with a timeout, in case something goes wrong).
It's possible that even if you don't wait, a Task could occasionally finish up - that's going to be indeterminate, depending on the timing in any individual run.
Can you clarify what happens in the main thread without the Wait or Join?
Related
I am trying to silently send a synchronous Lisp command to autocad from c# code.
Here's how we send a Synchronous command to autocad.
string textToSend = $"(command-s \"_.-layout\" \"_t\" {filePath} {layoutName})";
object acadDoc = Application.DocumentManager.MdiActiveDocument.GetAcadDocument();
acadDoc.GetType().InvokeMember("SendCommand", BindingFlags.InvokeMethod, null, acadDoc, new[] { textToSend + "\n" });
The command works but the problem is that the command ends up in autocad's command line and clogs up the history of the drafters using our extensions.
We tried modifying system variables CMDECHO, CMDDIA, and NOMUTT without success
Directly in autocad's command line manually
With the c# method SetSystemVariable()
The same way we called our InvokeMember("SendCommand")
In the same Lisp command where we do our action
I looked at the InvokeMember parameters but didn't see anything that might affect the display of the command like there exists for the ActiveDocument.SendTextToExecute() asynchronous command.
How do we send synchronous Lisp commands to autocad from c# code silently?
Ps: The reason why I am not using WBlockCloneObjects() is because it makes our apps extremely unstable. I am not really interested in opening that whole can of worms in this issue, I'm only stating this to explain why I ended up with the solution from my question.
The title of my question was misleading. I didn't need to run lisp code silently, I needed to run commands in acad's command line silently. It just happened to be that acad's command line accepts lisp code so that's what I was using.
Instead of running lisp code, I used the method ActiveDocument.Editor.Command() to send my command to autocad. This method is synchronous and is affected by the system variable cmdecho.
I encountered a subsequent problem; because I was calling this method from a button in the banner with RelayCommand, Editor.Command was throwing the exception eInvalidInput because I was in the Application context instead of the Document context.
The best way to handle this was to split my first method in two and call the second method with ActiveDocument.SendStringToExecute() which uses the command line so I end up in the Document context. But because SendstringToExecute() is async, I had to rework my method's logic a bit.
Here is the final result (simplified)
private Layout Layout;
private RelayCommand _Command_Test;
public RelayCommand Command_Test
{
get
{
if (_Command_Test == null)
{
_Command_Test = new RelayCommand(
(param) =>
{
FirstMethod();
},
(param) =>
{
return true;
}
);
}
return _Command_Test;
}
}
[CommandMethod("FirstMethod")]
public void FirstMethod()
{
// Many actions and processing
Layout = new Layout(/*With stuff*/);
Application.DocumentManager.MdiActiveDocument.SendStringToExecute("SecondMethod ", true, false, false);
}
[CommandMethod("SecondMethod")]
public void SecondMethod()
{
short cmdecho = (short)Application.GetSystemVariable("cmdecho");
Application.SetSystemVariable("cmdecho", 0);
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.Command("_.-layout", "_t", Layout.FilePath, Layout.Name);
Application.SetSystemVariable("cmdecho", cmdecho);
// Actions that need to be ran after the command
}
I had to split my first method differently to have two sets of synchronous actions.
Because CommandMethod can't receive parameters, I had to store information in the fields of the class.
I skipped it in the code shown here but I, of course, used try-catches and transactions to handle potential issues.
I would like to control the flow of information into a C# plugin I am writing for Battlefield 4. From my plugin, I must send a command to the Battlefield 4 server. There is a method in the plugin manager which I can't touch that will receive the response (from a command I sent via the plugin) from the server and pass back the information to the event as parameters.
If no information is controlled, plugins can run code without restraint and commands from external sources (users, other plugins) can tamper with normal plugin execution.
private void PerformProconTasks(params string[] actions) //A list of commands that will be processed.
{
foreach(var action in actions)
{
var command = action.Split(' ');
RegisterEvents(this.GetType().Name, InvocationDictionary[command[0]]); //The event will not be ignored by registering it now.
ProconPluginEXT.ExecuteCommand(this, command); //Sends command to the Battlefield 4 server
//Problem: We want to wait for the invoked event to complete before continuing with the loop. We do not know when the invoked event will occur neither do we know how long it will take for it to finish.
}
}
public override void On3dSpotting(bool isEnabled) //This is an example: an event that is invoked after the server responds (true or false) to the command: "vars.3dSpotting".
{
this.ExecuteCommand("procon.protected.pluginconsole.write", "^6Hello from the other side!");
}
I would call the next command from the event but I believe this approach limits the uses for that function. Anything about the event can change (return type, access, other modifiers) except the name and the parameters.
I'm just wondering if I can halt performprocontasks until the corresponding event is finished, even when I don't know when the corresponding event will fire and have no access to the data it will receive.
Edit: ExecuteCommand is void unfortunately and does not directly invoke On3dSpotting.
All it does is send the action/command to the battlefield server. The PluginManager is what invokes On3dSpotting and it is unreachable.
Also, the issue I am having with tasks is that I do not know when to create the task since I do not know when the event will fire and how to hook up the task to something that will exist eventually.
Thanks
This is my first Topic here and I didn't find any similar Topics so I try to describe my problem as good as I can:
I was ordered by my Company to create a modular C# program to assist our Software Developers with Background tasks. The Programm is composed of a Windows Forms application with a User Interface that calls external DLLs that do the actual work. All These DLLs are written by me aswell and follow certain rules to make them compatible to the Main App. That way I can easily add new funcions to the Programm just by putting the DLL into a predefined Folder. So to say Plug-and-Run
The main program contains a ListBox that shows all available PlugIns and if one get's selected and the "start" button is clicked, the Main program calls the corresponding DLL and Invokes the method "program" that starts the DLLs actual function. Furthermore the Main contains a method "Output" that is supposed to write the result of every PlugIn into a Tab of my TabControl. That way the results of every PlugIn running in separate threads can be viewed independently. The Access to the tab already has a delegate to make it threadsafe. The Information is gathered by invoke from the PlugIn's own "returnOutput" method that simply Returns a List of strings containing the results to the Main.
My Problem now is: How can i implement a Kind of a callback into my PlugIn DLLs so they can order the Main Program to gather the results at any time?
My first idea was to simply add the result as return values to the "program" method itself but that would make the Information only available at the end of the program and some of the Tasks require a "live update" during runtime.
My second idea was to use the delegate for the Control as Parameter and pass it to the PlugIn so the PlugIn DLL could Access the Control on it's own. This idea failed because the DLL doesn't "know" the Main program and can't Access it's Methods or the delegates instance so I am Always missing a reference.
Is there a way to solve my problem? If necessary I can provide Code snippets but the program has already around 800 lines of Code and each PlugIn adds a few hundred more..
Thanks in advance for every answer and sorry for my non-native english :D
Best Regards
Gerrit "Raketenmaulwurf" M.
Edit: I am using SharpDevelop 5.1
Code Snippet for the DLL call:
PlugIn = PlugIns.SelectedItem.ToString();
Assembly PlugInDLL = Assembly.LoadFile(#PlugInOrdner+"\\"+PlugIn+".dll");
Object Objekt = PlugInDLL.CreateInstance("DLL.PlugIn");
MethodInfo Info1 = Objekt.GetType().GetMethod("Programm");
Info1.Invoke(Objekt, new Object[]{Projekt, TIAInstanz});
it basically Looks for a DLL file that has the same Name as the highlighted item in the ListBox
There are many different ways to do this. Some of the suggestions in the comments are really good and implementing them would make a robust and extendable solution.
If you are looking for a quick and easy way to get messages from your plugins, though, then you can pass your callback directly to the plugin as an Action:
public class PluginRunner
{
public class PluginMessageEventArgs
{
public string Text { get; set; }
}
public event EventHandler<PluginMessageEventArgs> PluginMessage;
public void Run( string pluginPath )
{
Assembly PlugInDLL = Assembly.LoadFile(pluginPath);
Object Objekt = PlugInDLL.CreateInstance("DLL.PlugIn");
MethodInfo Info1 = Objekt.GetType().GetMethod("Programm");
Info1.Invoke(Objekt, new Object[] { Projekt, TIAInstanz, new Action<string>(Log) });
}
private void Log(string s)
{
PluginMessage?.Invoke(this, new PluginMessageEventArgs { Text = s });
}
}
so you can use it like:
var path =
Path.Combine(
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
"Plugins",
"MyAwesomePlugin.dll");
var pr = new PluginRunner();
// be aware that your event delegate might be invoked on a plugin's thread, not the application's UI thread!
pr.PluginMessage += (s,e) => Console.WriteLine("LOG: " + e.Text);
pr.Run(path);
then your plugin's Programm method writes its logs:
public void Programm( ProjektClass p0, TIAClass p1, Action<string> log )
{
Task.Run(() =>
{
// do something
log.Invoke("here am I!");
// do something else
log.Invoke("here am I again!");
// do something more
});
}
I must admit, that this is not the ideal way to deal with messaging. There are far better (and, unfortunately, more complicated to implement) solutions out there. This one is fairly simple though. Just don't forget that you receive your message on the same thread that have sent it and avoid deadlocks.
So I have a huge program and decided I should make one of the methods run in a separate thread. So I put the method in a separate class, an activated it on my form. It seemed to worked just how I wanted it to until it got to part where it gave me this error:
SendKeys cannot run inside this application because the application
is not handling Windows messages. Either change the application to
handle messages, or use the SendKeys.SendWait method.
I tried looking for the answer online. I think I saw something about how SendKeys only works in a Form or something.
Can anyone tell me a way to simulate a keystroke without using SendKeys, OR a way to get SendKeys to work in a different, non-form thread?
Your console application needs a message loop. This is done through the Application class. You will need to call Application.Run(ApplicationContext).
class MyApplicationContext : ApplicationContext
{
[STAThread]
static void Main(string[] args)
{
// Create the MyApplicationContext, that derives from ApplicationContext,
// that manages when the application should exit.
MyApplicationContext context = new MyApplicationContext();
// Run the application with the specific context. It will exit when
// the task completes and calls Exit().
Application.Run(context);
}
Task backgroundTask;
// This is the constructor of the ApplicationContext, we do not want to
// block here.
private MyApplicationContext()
{
backgroundTask = Task.Factory.StartNew(BackgroundTask);
backgroundTask.ContinueWith(TaskComplete);
}
// This will allow the Application.Run(context) in the main function to
// unblock.
private void TaskComplete(Task src)
{
this.ExitThread();
}
//Perform your actual work here.
private void BackgroundTask()
{
//Stuff
SendKeys.Send("{RIGHT}");
//More stuff here
}
}
I Know this not an answer, but this how i used to do using ActiveX and Script
Set ws = CreateObject("WScript.Shell")
str = "Hi there... ~ Dont click your mouse while i am typing." & _
" ~~This is a send key example, using which you can send your keystrokes"
ws.Run("notepad.exe")
WScript.Sleep(1000)
For c=1 To Len(str)
WScript.Sleep(100) 'Increase the value for longer delay
ws.SendKeys Mid(str,c,1)
Next
Save this code as file.vbs and double click to execute in windows machine.
Generally with services, the task you want to complete is repeated, maybe in a loop or maybe a trigger or maybe something else.
I'm using Topshelf to complete a repeated task for me, specifically I'm using the Shelf'ing functionality.
The problem I'm having is how to handle the looping of the task.
When boot strapping the service in Topshelf, you pass it a class (in this case ScheduleQueueService) and indicate which is its Start method and it's Stop method:
Example:
public class QueueBootstrapper : Bootstrapper<ScheduledQueueService>
{
public void InitializeHostedService(IServiceConfigurator<ScheduledQueueService> cfg)
{
cfg.HowToBuildService(n => new ScheduledQueueService());
cfg.SetServiceName("ScheduledQueueHandler");
cfg.WhenStarted(s => s.StartService());
cfg.WhenStopped(s => s.StopService());
}
}
But in my StartService() method I am using a while loop to repeat the task I'm running, but when I attempt to stop the service through Windows services it fails to stop and I suspect its because the StartService() method never ended when it was originally called.
Example:
public class ScheduledQueueService
{
bool QueueRunning;
public ScheduledQueueService()
{
QueueRunning = false;
}
public void StartService()
{
QueueRunning = true;
while(QueueRunning){
//do some work
}
}
public void StopService()
{
QueueRunning = false;
}
}
what is a better way of doing this?
I've considered using the .NET System.Threading.Tasks to run the work in and then maybe closing the thread on StopService()
Maybe using Quartz to repeat the task and then remove it.
Thoughts?
Generally, how I would handle this is have a Timer event, that fires off a few moments after StartService() is called. At the end of the event, I would check for a stop flag (set in StopService()), if the flag (e.g. your QueueRunning) isn't there, then I would register a single event on the Timer to happen again in a few moments.
We do something pretty similar in Topshelf itself, when polling the file system: https://github.com/Topshelf/Topshelf/blob/v2_master/src/Topshelf/FileSystem/PollingFileSystemEventProducer.cs#L80
Now that uses the internal scheduler type instead of a Timer object, but generally it's the same thing. The fiber is basically which thread to process the event on.
If you have future questions, you are also welcomed to join the Topshelf mailing list. We try to be pretty responsive on there. http://groups.google.com/group/topshelf-discuss
I was working on some similar code today I stumbled on https://stackoverflow.com/a/2033431/981 by accident and its been working like a charm for me.
I don't know about Topshelf specifically but when writing a standard windows service you want the start and stop events to complete as quickly as possible. If the start thread takes too long windows assumes that it has failed to start up, for example.
To get around this I generally use a System.Timers.Timer. This is set to call a startup method just once with a very short interval (so it runs almost immediately). This then does the bulk of the work.
In your case this could be your method that is looping. Then at the start of each loop check a global shutdown variable - if its true you quit the loop and then the program can stop.
You may need a bit more (or maybe even less) complexity than this depending on where exactly the error is but the general principle should be fine I hope.
Once again though I will disclaim that this knowledge is not based on topshelf, jsut general service development.