I'm a beginner!
I made a wrapper DLL (DLL_A) of a third-party DLL (DLL_B).
Below you can find a simplyfied example:
The DLL_B class Class_B expose (i can view only the signature from metadata):
public delegate void eveHandler(bool ret_B);
public class cls_B
{
public cls_B(string init);
public event eveHandler eve;
public void req(eveHandler reqHandler = null);
}
Inside DLL_A:
public class cls_A
{
private cls_B objClsB;
private bool continueWorking = true;
public cls_A()
{
objClsB = new cls_B("test");
objClsB.ev += new eveHandler(this.eveManager);
}
public eveManager(bool ret_A)
{
continueWorking = false;
}
public request()
{
objClsB.req();
int i = 0;
While (continueWorking && i < 100)
{
//Do a lot of stuff...
i++;
}
}
}
Then inside the main app:
cls_A objClsA = new cls_A();
objClsA.request();
MessageBox.Show("Done!", "MyApp");
It works, but it seems that the eveManager() is only called when it exit from the objClsA.request(); , before execute MessageBox.Show("Done!", "MyApp");.
In fact if I remove the && i < 100 part it will stuck inside the while loop, but I need that the event stop the loop.
Where I'm wrong?
Thanks in advance!
Solution founded! It works if I start the objClsB.req() on another thread this way:
public request()
{
Thread thr = new Thread(delegate() { objClsB.req(); });
thr.Start();
int i = 0;
While (continueWorking)
{
//Do a lot of stuff...
}
}
Related
I have a small WPF application written primarily following MVVM pattern. The point of the program is to read the lines of a text file, parse the data from them, write that data to a list of objects, then write the data in those objects into a specifically-formatted .CSV file.
Even though I have implemented the BackgroundWorker class itself the same way I always have with other applications, this time I am invoking the RunWorkAsync() method from within the Execute() method of my ICommand. While the final output is correct and the application actually delivers the desired result, the UI STILL locks up while the BackgroundWorker is running.
I have wrapped my BackgroundWorker members and all the logic inside a class named "ReaderWriter" with a constructor that takes my ViewModel as a parameter.
By calling the "StartProcess" method of my ReaderWriter instance, the BackgroundWorker's RunWorkerAsync() is called -- this is where I was hoping it would take over on another thread and do my long-running process of reading the source file, parsing the data, and writing the new file; all while periodically doing ReportProgress() to update the ProgressBar.
Here is the code for my ReaderWriter class:
class ReaderWriter
{
private LogDataViewModel vm { get; set; }
private BackgroundWorker bw { get; set; }
public ReaderWriter(LogDataViewModel viewModel)
{
vm = viewModel;
}
public void StartProcess()
{
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(ReadFromSource);
bw.ProgressChanged += new ProgressChangedEventHandler(UpdateProgress_Read);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Completed_Read);
bw.RunWorkerAsync();
}
private void ReadFromSource(object sender, DoWorkEventArgs e)
{
double records = 0;
string[] lines = File.ReadAllLines(vm.SourcePath);
int lineCount = lines.Length;
double currentLine = 0;
bw.ReportProgress(0, lineCount);
foreach (var line in lines)
{
if (line.Length > 0)
{
string syntax = line.Substring(17, 6);
switch (syntax)
{
case "$WIMDA":
string[] segments = line.Replace(": <- ", ",").Split(',');
vm.LineItems.Add(new LineItem()
{
Time = segments[0],
HgPressure = segments[2],
BarPressure = segments[4],
AirTemp = segments[6],
RelHumidity = segments[10],
TrueWindDir = segments[14],
KnotsWindSpeed = segments[18],
MpsWindSpeed = segments[20]
});
break;
case "$GPGGA":
break;
default:
break;
}
}
currentLine++;
bw.ReportProgress(1, currentLine);
}
using (StreamWriter writer = new StreamWriter(vm.OutputPath))
{
writer.WriteLine($"Time,Pressure(Bar),Pressure(Hg),AirTemp({((vm.ConvertTempSetting) ? "F" : "C")}),RelativeHumidity,TrueWindDirection,WindSpeed(Knots),WindSpeed(M/s)");
foreach (var lineItem in vm.LineItems)
{
writer.WriteLine($"{lineItem.Time},{lineItem.BarPressure},{lineItem.HgPressure},{((vm.ConvertTempSetting) ? Converters.ConvertFromCelcius(Convert.ToDouble(lineItem.AirTemp)).ToString() : lineItem.AirTemp)},{lineItem.RelHumidity},{lineItem.TrueWindDir},{lineItem.KnotsWindSpeed},{lineItem.MpsWindSpeed}");
records++;
}
}
e.Result = records;
}
private void UpdateProgress_Read(object sender, ProgressChangedEventArgs e)
{
vm.IncrementProgress();
switch (Type.GetTypeCode(e.UserState.GetType()))
{
case TypeCode.Double:
vm.IncrementProgress();
break;
case TypeCode.String:
break;
case TypeCode.Int32:
vm.AppendStatus(DateTime.Now, $"{(int)e.UserState} lines parsed from log file");
break;
default:
break;
}
if (vm.IsFirst)
{
vm.ProgressIsVisible = true;
vm.IncrementProgress();
vm.SetMaximum((int)e.UserState);
vm.IsFirst = false;
}
}
private void Completed_Read(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
vm.AppendStatus(DateTime.Now, $"Conversion was cancelled by user");
}
else
{
vm.AppendStatus(DateTime.Now, $"{(double)e.Result} records written to {vm.OutputPath}");
}
vm.LineItems.Clear();
}
}
And for my ViewModel:
public class LogDataViewModel : LogDataModel
{
#region Commands
public BeginProcessCommand BeginCommand { get; set; }
public SelectOutputPathCommand OutputCommand { get; set; }
public SelectSourceCommand SourceCommand { get; set; }
public ResetCommand ResetCommand { get; set; }
#endregion
public bool IsFirst { get; set; }
public LogDataViewModel()
{
BeginCommand = new BeginProcessCommand(this);
OutputCommand = new SelectOutputPathCommand(this);
SourceCommand = new SelectSourceCommand(this);
ResetCommand = new ResetCommand(this);
PrepareViewModel();
}
private void PrepareViewModel()
{
ProgressValue = 0;
ProgressMaximum = 0;
ProgressIsVisible = false;
IsFirst = true;
OutputPath = Properties.Settings.Default.RememberedSavePath;
if (LineItems == null) LineItems = new List<LineItem>();
if (StatusActions == null) StatusActions = new ObservableCollection<StatusAction>();
AppendStatus(DateTime.Now, "Initialized Program");
}
}
And lastly, here is the Command:
public class BeginProcessCommand : ICommand
{
LogDataViewModel vm;
public BeginProcessCommand(LogDataViewModel viewModel)
{
vm = viewModel;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
bool result = true;
if (!File.Exists(vm.SourcePath))
result = false;
try
{
if (!Directory.Exists(Path.GetDirectoryName(vm.SourcePath)))
result = false;
}
catch
{
result = false;
}
return result;
}
public void Execute(object parameter)
{
ReaderWriter rw = new ReaderWriter(vm);
rw.StartProcess();
}
}
Any help at this point is very much appreciated, as I've struggled with this for a while now and any attempt to research solutions yield no help for my particular situation. This seems like a fairly unique scenario, but I am hoping we can make it work.
Thank you!
You are using ReportProgress incorrectly and far too often (on every line in the file). It will be being hammered and every call is causing some sort of update in your UI hence locking it up.
ReportProgress is probably easiest to use by passing a percentage to it. I'm not really sure what you are doing in UpdateProgress_Read with the switch. It would be best to only update as you pass a 100th of your total lines.
set your progressBar maximum to 100
ProgressMaximum = 100;
calculate 1% of your total lines
var x = lineCount / 100;
var y = 0;
and only Report progress as you pass each 1%
currentLine++;
if((currentLine % x) == 0)
{
y++;
bw.ReportProgress(y);
}
and change UpdateProgress_Read so it just increments
private void UpdateProgress_Read(object sender, ProgressChangedEventArgs e)
{
vm.IncrementProgress();
}
you'll need to come up with better variable names then x and y! and also work out what to do if you have less than 100 lines in the file.
I have a winforms application with a singleton class that contains a property (list) that is the datasource for a search grid. Filling this property takes a significant amount of time (>1 minute) so, I want to start filling this property asynchronously when the user launches the program.
The main form has a button to launch another search form. If, when the user launches the search if the datasource is ready then, no problem. However, if the datasource is still filling, the user sees a wait cursor and the search grid should fill as soon as the datasource has finished populating.
To do this, I created a method that fires after the asynchronous method completes and then the grid is bound to the datasource.
Everything appears to work correctly, the event fires, and then I try to bind the list to the grid and nothing happens... Debugging halts and I never hit the next line of code (see comments in FrmSearch.cs).
Any ideas relating to what is going wrong or general code improvements would be very much appeciated, thanks!
Program.cs
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Task.Run(async() => { Singleton.DogsList = await Dogs.FindAllAsync(); });
Application.Run(new FrmMain());
}
}
Singleton.cs
public static class Singleton
{
public static event DogsListHandler DogsListLoaded;
public delegate void DogsListHandler(object sender, EventArgs e);
public static BindingList<Dogs> DogsList
{
get
{
if (dogsList == null)
{
Task.Run(async () =>
{
dogsList = await Dogs.FindAllAsync();
notifyListLoaded();
});
}
return dogsList;
}
set { dogsList = value; }
}
private static BindingList<Dogs> dogsList;
private static void notifyListLoaded()
{
if (DogsListLoaded != null) { DogsListLoaded(null, EventArgs.Empty); }
}
}
FrmSearch.cs
public partial class FrmSearch : Form //launched using the .Show() method from a button on the main form
{
public FrmSearch()
{
InitializeComponent();
}
private void FrmSearch_Load(object sender, EventArgs e)
{
Singleton.DogsListLoaded += new Singleton.DogsListHandler(Dogs_ListLoaded);
Cursor = Cursors.WaitCursor;
if (Singleton.DogsList != null)
{
grid.DataSource = Singleton.DogsList;
Cursor = Cursors.Default;
}
else { Cursor = Cursors.WaitCursor; }
}
public void Dogs_ListLoaded(object sender, EventArgs e)
{
grid.DataSource = Singleton.DogsList; //freezes here
Cursor = Cursors.Default; //this line never gets hit
}
}
Dogs.cs (will pull from db normally but just doing some iteration for the sample)
public class Dogs
{
public string Name { get; set; }
public string Breed { get; set; }
public int Age { get; set; }
public string Summary { get { return string.Format("Name: {0} / Breed: {1} / Age: {2}", Name, Breed, Age.ToString()); } }
public static async Task<BindingList<Dogs>> FindAllAsync()
{
BindingList<Dogs> dl = new BindingList<Dogs>();
await Task.Run(() =>
{
int i = 0;
while (i <= 999999)
{
dl.Add(new Dogs() { Name = "River" + i.ToString(), Breed = "Border Collie", Age = 3 });
dl.Add(new Dogs() { Name = "Jack" + i.ToString(), Breed = "Labrador", Age = 2 });
dl.Add(new Dogs() { Name = "Emma" + i.ToString(), Breed = "Beagle", Age = 7 });
i++;
}
});
return dl;
}
}
You should retrieve the list as you would retrieve all other lists getting from anywhere:
Call a async method and await that.
Easy to handle
public static class SomeLookups
{
private static object _lock = new object( );
private static Task<IList<string>> _foosAsync;
public static Task<IList<string>> GetFoosAsync()
{
lock ( _lock )
{
return _foosAsync = _foosAsync ?? PrivateGetFoosAsync( );
}
}
private static async Task<IList<string>> PrivateGetFoosAsync()
{
var list = new List<string>( );
for ( int i = 0; i < 20; i++ )
{
await Task.Delay( 200 ).ConfigureAwait( false );
list.Add( "item " + i );
}
return list.AsReadOnly( );
}
}
In the consuming class
private async Task RetrieveAllData()
{
IsBusy = true;
MyFooCollection = await SomeLookups.GetFoosAsync();
IsBusy = false;
}
Once the task is finished you can still await it. But because it is finished you get the result without any delay.
i have an application which turns your webcam on and off. It turns it on, on a new thread and im trying to make a button work from the main code to turn it off buts cross threading doesnt work and as their is no form, i cant invoke. here is the code:
public class Program
{
public static FilterInfoCollection CaptureDevicesList;
public static VideoSourcePlayer videoSourcePlayer = new VideoSourcePlayer();
public static VideoCaptureDevice videoSource;
public static System.Timers.Timer TimerClose = new System.Timers.Timer();
[STAThread]
static void Main()
{
TimerClose.Elapsed += (o, e) => TimerClose_Tick();
TimerClose.Interval = 10000;
TimerClose.Start();
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
CaptureDevicesList = new FilterInfoCollection(FilterCategory.VideoInputDevice);
Class1.oncam();
}).Start();
Application.Run();
}
private static void TimerClose_Tick()
{
Class1.CloseCurrentVideoSource(); // <--- function cant run
}
}
So what im trying to do is get the close function to work which is trying to turn the webcam off which is running on a different thread. Here is class1:
class Class1
{
public static void oncam()
{
Program.videoSource = new VideoCaptureDevice(Program.CaptureDevicesList[0].MonikerString);
OpenVideoSource(Program.videoSource);
}
public static void OpenVideoSource(IVideoSource source)
{
CloseCurrentVideoSource();
Program.videoSourcePlayer.VideoSource = source;
Program.videoSourcePlayer.Start();
}
public static void CloseCurrentVideoSource()
{
if (Program.videoSourcePlayer.VideoSource != null)
{
Program.videoSourcePlayer.SignalToStop();
for (int i = 0; i < 30; i++)
{
if (!Program.videoSourcePlayer.IsRunning)
break;
System.Threading.Thread.Sleep(100);
}
if (Program.videoSourcePlayer.IsRunning)
{
Program.videoSourcePlayer.Stop();
}
Program.videoSourcePlayer.VideoSource = null;
}
}
}
Any help is appriciated
VideoSourcePlayer inherits from System.Windows.Forms.Control and thus requires synchronization for cross-thread access.
Call Invoke on this control when accessing it from another thread.
I have an issue with data seemingly being reset to its default values.
The class is as follows (objectIDs is a simple enumeration):
public class Output_args: EventArgs {
public objectIDs outputtype;
public int internalID;
public int verdict;
public int outputID;
public long entrytime;
public Output_args Copy() {
Output_args args = new Output_args();
args.entrytime = this.entrytime;
args.internalID = this.internalID;
args.outputID = this.outputID;
args.outputtype = this.outputtype;
args.verdict = this.verdict;
return args;
}
}
The following code creates the object. It runs in a specific thread, let's say Thread1.
class Class1 {
EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset);
public event EventHandler<Output_args> newOutput;
public void readInput(){
List<Output_args> newoutputlist = new List<Output_args>();
/*
* code to determine the outputs
*/
Output_args args = new Output_args();
args.outputtype = objectIDs.stepID;
args.internalID = step[s].ID;
args.verdict = verdict;
args.entrytime = System.DateTime.Now.Ticks;
newoutputlist.Add(args.Copy());
if (newOutput != null && newoutputlist.Count > 0) {
// several outputs are being sent sequentially but for simplicity i've removed the for-loop and decision tree
try {
newOutput(null, newoutputlist[0].Copy());
} catch (Exception) { }
}
}
}
1 of the subscribers to this event has the following code. The processor method runs on a thread of a camerafeed. The newOutput event handler is being run on Thread1.
class Class2: Form {
private Output_args lastoutput = new Output_args();
public void newOutput(object sender, Output_args args) {
lock (lastoutput) {
lastoutput = args.Copy();
}
}
public void processor(){
lock (lastoutput) {
if (lastoutput.entrytime + 10000000 > System.DateTime.Now.Ticks) {
// do something
}
}
}
}
When the eventhandler 'newOutput' of Class2 is being called, the debugger shows that the copy works as expected and 'entrytime' is given the expected number of ticks.
However, when the processor method wants to read the 'entrytime', its value is 0. All other fields also have their default value assigned.
I've tried replacing the object 'lastoutput' with a simple field of the type long and removed the locks but the results are the same: it gets assigned properly in 'newOutput' but has its default value (0) in the processor method.
Any ideas on why this is happening?
you should not lock on the object lastoutput, but on another object, because you reassign the field.
The processor start and lock on the default field instance new Output_args() initialized with default values
class Class2: Form {
private object mylock = new object();
private Output_args lastoutput;
public void newOutput(object sender, Output_args args) {
lock (mylock) {
lastoutput = args.Copy();
}
}
public void processor(){
lock (mylock) {
if (lastoutput == null) {
//nothing to consume yet
}
else if (lastoutput.entrytime + 10000000 > System.DateTime.Now.Ticks) {
// do something
}
}
}
}
but this discard lastouput if consumer is slower than producer. You can use a queue ( or another collection ) as buffer if needed.
class Class2 {
private Queue<Output_args> outputs = new Queue<Output_args>();
public void newOutput(object sender, Output_args args) {
lock (outputs) {
outputs.Enqueue(args.Copy());
}
}
public void processor(){
lock (outputs) {
if (outputs.Count > 0) {
var lastoutput = outputs.Dequeue();
if (lastoutput.entrytime + 10000000 > System.DateTime.Now.Ticks) {
// do something
}
}
}
}
}
demo: https://dotnetfiddle.net/daHVD1
I have implemented callbacks on Handler(in java). I need to implement the same on c#(Xamarin). But as of now I am unable to find any solution to how I can do this in C#. I am new to c# hence have very little knowledge.
here is the java code:-
private Handler handler = new Handler(new Handler.Callback() {
#Override
public boolean handleMessage(Message msg)
{
if (msg.what == MSG_SURFACE_CREATED)
{
contentWidth = 0;
contentHeight = 0;
requestLayout();
return true;
}
else
{
Log.w("Unknown msg.what: " + msg.what);
}
return false;
}
});
Any idea can i implement the same in C#?
Events is the way to go (which uses delegates). Here is some sample code:
class CallingClass
{
private SurfaceCreatingClass m_surfacecreatingclass;
public CallingClass()
{
m_surfacecreatingclass = new SurfaceCreatingClass();
m_surfacecreatingclass.SurfaceCreatedHandler += OnFinished;
m_surfacecreatingclass.CreateSurface();
}
void OnFinished(int iMessageWhat)
{
if (iMessageWhat == SurfaceCreatingClass.MSG_SURFACE_CREATED)
{
contentWidth = 0;
contentHeight = 0;
RequestLayout();
}
else
{
Log.w("Unknown msg.what: " + iMessageWhat);
}
}
}
class SurfaceCreatingClass
{
public delegate void SurfaceCreatedDelegate(int iWhat);
public event SurfaceCreatedDelegate SurfaceCreatedHandler;
public const int MSG_SURFACE_CREATED = 1;
public void CreateSurface()
{
/////////////////////////////
// Surface creation code here
// ...
/////////////////////////////
if (SurfaceCreatedHandler != null)
SurfaceCreatedHandler(MSG_SURFACE_CREATED);
}
}
IN Xamarin You still will use this " inline " approach, defined by the keyword "delegate".
This is, because, callbacks are function pointers, c# supports this, java not, and xamarin also not, BUT tries to spawn a bridge.
Delegates in xamarin are described in here:
http://docs.xamarin.com/guides/ios/application_fundamentals/delegates,_protocols,_and_events/