i'm really struggeling with OOP. I would like to start a process in my additional class. The process is a shell and I need to access this shell from severel forms and classes to write the commands and to receive the output. I use events to get the data. Here is my class for the process.
My class for the
public class ADBShell
{
public static string output = String.Empty;
public static Process adbshell = new Process();
public void Start_ADBShell()
{
if (adbshell != null && !adbshell.HasExited)
return;
adbshell = new Process();
adbshell.StartInfo.UseShellExecute = false;
adbshell.StartInfo.FileName = #"D:\adb\adb.exe";
adbshell.StartInfo.Arguments = "shell";
adbshell.StartInfo.RedirectStandardOutput = true;
adbshell.StartInfo.RedirectStandardInput = true;
//adb.StartInfo.RedirectStandardError = true;
adbshell.EnableRaisingEvents = true;
adbshell.StartInfo.CreateNoWindow = true;
//adb.ErrorDataReceived += new DataReceivedEventHandler(adb_ErrorDataReceived);
adbshell.OutputDataReceived += new DataReceivedEventHandler(adbshell_OutputDataReceived);
try { var started = adbshell.Start(); }
catch (Exception ex)
{
Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
}
//adb.BeginErrorReadLine();
adbshell.BeginOutputReadLine();
}
void adbshell_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
output += (e.Data) + Environment.NewLine;
}
public void press_touch(string x, string y)
{
adbshell.StandardInput.WriteLine("input tap " + String.Format("{0} {1}", x, y));
Debug.WriteLine("pressed");
}
}
My Form class looks like
public partial class Form1 : Form
{
private bool _record;
private bool _selecting;
private Rectangle _selection;
//---------------------------------------------------------------------
public Form1()
{
InitializeComponent();
}
//---------------------------------------------------------------------
private void Form1_Load(object sender, System.EventArgs e)
{
ADBShell adbshell = new ADBShell();
adbshell.Start_ADBShell();
}
Everytime I have to make a new object in my methods, but i dont want to create everytime a new object. I would like make one time the object and access everytime to the same object. I do not want to make servel processes. I need only proccess and send and receive everytime the data to this process.
Do I have to make a static class?
How I can dispose and close my process after I'm quit my Form Class?
1: You do not want a static class. You want a SINGLETON - that is a class that has only one instance. This is normally accessed using a static property. At the easiest way this works like this:
public class A () {
private A () {}
public static A Instance {get; } = new A();
}
Access is via:
A.Instance
2: You do not. Processes do not get disposed. You exit the last thread that is not a background thread then the process ends. Otherwise you kill it, if that has to be done "In force" from the outside.
Move the ADBShell intialization in constructor of Form class. So this object will live till Form is not exited and to release resources by process make sure you call Process.close() in ADBShell class (Either in destructor or implement a IDisposable)
public partial class Form1 : Form
{
private bool _record;
private bool _selecting;
ADBShell adbshell;
private Rectangle _selection;
//---------------------------------------------------------------------
public Form1()
{
InitializeComponent();
adbshell = new ADBShell();
}
//---------------------------------------------------------------------
private void Form1_Load(object sender, System.EventArgs e)
{
adbshell.Start_ADBShell();
}
Dipose Process like this by adding Destructor
~ADBShell()
{
process.Close();
}
or implement Dispose method of IDisposable
Class ABDShell : IDisposable
{
...
...
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
process.Close();
}
}
}
Updated singleton class
sealed class ADBShell
{
public static string output = String.Empty;
private ABDShell _instance;
private Process _processInstance;
// Note: constructor is 'private'
private ADBShell()
{
}
public Process ProcessInstance
{
if(_processInstance==null)
_processInstance = new Process();
get _processInstance ;
}
public static ADBShell Instance
{
get
{
if (_instance == null)
{
_instance = new ABDShell();
}
return _instance;
}
}
}
Now from your Form just do this
Process process = ABDShell.Instance.ProcessInstance;
// Sealed class makes sure it is not inherited. If inheritance required, go to Abstract Pattern.
class ADBShell
{
//public static property used to expose Singleton instance.
public static ADBShell Instance;
// private constructor
private ADBShell() { }
public static ADBShell getInstance()
{
if (Instance == null)
{
Instance = new Process;
}
}
}
Update
Thank you with your helps I solved my problems and now the ADB runs much faster instead of start everytime a new process.
public class ADBShell
{
private static ADBShell instance;
//private List<Employee> employeeList = null;
private Process shell = null;
private StreamWriter myWriter = null;
private static readonly object syncRoot = new object();
private ADBShell()
{
if (shell == null)
{
shell = new Process();
shell.StartInfo.FileName = (#"D:\ADB\ADB.exe");
shell.StartInfo.Arguments = "shell";
shell.StartInfo.RedirectStandardInput = true;
shell.StartInfo.UseShellExecute = false;
shell.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
shell.StartInfo.RedirectStandardOutput = true;
shell.StartInfo.CreateNoWindow = true;
shell.EnableRaisingEvents = true;
shell.OutputDataReceived += (sender, a) => Console.WriteLine(a.Data);
shell.Start();
myWriter = shell.StandardInput;
shell.BeginOutputReadLine();
}
}
public static ADBShell Instance()
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new ADBShell();
}
}
}
return instance;
}
public void tap(int x, int y)
{
myWriter.WriteLine("input tap {0} {1}", x.ToString(), y.ToString());
Thread.Sleep(10);
}
public void tap(string x, string y)
{
myWriter.WriteLine("input tap {0} {1}", x, y);
Thread.Sleep(10);
}
public void exit()
{
myWriter.WriteLine("exit");
}
public void Close()
{
myWriter.WriteLine("exit");
shell.WaitForExit();
if (!shell.HasExited)
{
shell.Kill();
}
shell.Close();
shell.Dispose();
myWriter.Close();
myWriter.Dispose();
}
}
Related
I am having a lot of trouble with this. Consider this example:
public class Test {
Thread t;
public Test() {
t = new Thread(ThreadFunction);
}
public void Start() {
t.Start();
}
private void ThreadFunction() {
Thread.Sleep(5000);
Console.WriteLine("Function Complete");
}
}
public static class Main {
public Main() {
Test test = new Test();
test.Start();
// sleep longer than my worker so it finishes
Thread.Sleep(10000);
// a place to place a breakpoint
bool breakPointHere = true;
}
}
Now, I see the output of the console.log, but when I inspect Test's thread object, I see that IsAlive is still true, and ThreadStatus = TheadStatus.Running. Why is this? I wish to detect that the thread is truly complete, but I am confused as to how it can still be considered running if ThreadFunction() completes?
EDIT 2:
I finally tracked down the cause, Updating the code, and am going to answer my own question
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1 {
public abstract class Worker {
protected bool shutdown;
protected Thread t;
private bool _isStopped = true;
public bool IsStopped {
get {
return t.ThreadState == ThreadState.Stopped;
}
}
private bool _isPaused = false;
public bool IsPaused {
get {
return _isPaused;
}
}
private string stringRepresentation;
public Worker() {
t = new Thread(ThreadFunction);
stringRepresentation = "Thread id:" + t.ManagedThreadId;
t.Name = stringRepresentation;
}
public void Start() {
OnBeforeThreadStart();
t.Start();
}
public void ScheduleStop() {
shutdown = true;
}
public void SchedulePause() {
OnPauseRequest();
_isPaused = true;
}
public void Unpause() {
_isPaused = false;
}
public void ForceStop() {
t.Abort();
}
/// <summary>
/// The main thread loop.
/// </summary>
private void ThreadFunction() {
OnThreadStart();
while (!shutdown) {
if (!IsPaused) {
if (!OnLoop()) {
break;
}
}
Thread.Sleep(1000);
}
OnShutdown();
}
public abstract void OnBeforeThreadStart();
public abstract void OnThreadStart();
public abstract bool OnLoop();
public abstract void OnShutdown();
public abstract void OnPauseRequest();
public override string ToString() {
return stringRepresentation;
}
}
public class Test : Worker {
public override void OnBeforeThreadStart() {
Log.WriteLine(this + ": Thread about to be started...");
}
public override void OnThreadStart() {
Log.WriteLine(this + ": Thread Started!");
}
public override bool OnLoop() {
Log.WriteLine(this + ": I am doing the things...");
return true;
}
public override void OnShutdown() {
Log.WriteLine(this + ": Shutting down!");
}
public override void OnPauseRequest() {
}
}
public static class Log {
public delegate void LogDelegate(string text, string eventTime, Severity severity);
public static event LogDelegate OnWriteLine;
private static Queue<string> _pendingFileWrites = new Queue<string>();
public enum Severity {
Info,
Warning,
Error
}
public static void WriteLine(object line, Severity severity = Severity.Info) {
string eventTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
string formatted = "[" + eventTime + "]: " + line;
Console.WriteLine(formatted);
lock (_pendingFileWrites) {
_pendingFileWrites.Enqueue(formatted);
}
if (OnWriteLine != null) {
// this is the offending line:
OnWriteLine.Invoke((string)line, eventTime, severity);
}
}
public static void WriteToFile(string path) {
lock(_pendingFileWrites) {
StreamWriter sw = File.AppendText(path);
while(_pendingFileWrites.Count > 0) {
sw.WriteLine(
_pendingFileWrites.Dequeue()
);
}
sw.Close();
}
}
}
class Program {
static void Main(string[] args) {
List<Test> tests = new List<Test>();
for(int i = 0; i < 10; i++) {
Test test = new Test();
test.Start();
tests.Add(test);
}
// sleep a little bit so they do the things
Thread.Sleep(10000);
foreach (Test test in tests) {
test.ScheduleStop();
}
bool allStopped;
do {
allStopped = true;
foreach (Test test in tests) {
if (!test.IsStopped) {
allStopped = false;
break;
}
}
} while (!allStopped);
Console.WriteLine("Done!");
// a place to place a breakpoint
bool breakPointHere = true;
}
}
}
I think your original testing that lead you to believe .IsAlive would be true had some flaw in it, I tweaked your program in your question to the following to make it compile and to be able to see which thread it created.
public class Program
{
public class Test
{
Thread t;
public Test()
{
t = new Thread(ThreadFunction);
t.Name = "TestThread";
}
public void Start()
{
t.Start();
}
private void ThreadFunction()
{
Thread.Sleep(5000);
Console.WriteLine("Function Complete");
}
}
public static void Main()
{
Test test = new Test();
test.Start();
// sleep longer than my worker so it finishes
Thread.Sleep(10000);
// a place to place a breakpoint
bool breakPointHere = true;
}
}
here is a screenshot of the running threads from inside ThreadFunction
Here is a screenshot from the end of the program
Notice that there is no "TestThread" thread.
Here is a screenshot from the locals window
IsAlive is false.
Do you really need to sleep to wait for your thread to finish?
If you don't, a better and more robust solution would be using Thread.Join()
public static class Main {
public Main() {
Test test = new Test();
test.Start();
test.Join(); // Waits for test to complete
bool breakPointHere = true;
}
}
So it turns out that my issue was that my logging method was calling a UI thread function like so:
private void LogToForm(object line, string eventTime, Log.Severity severity) {
if (dataGridView_LogInfo.InvokeRequired) {
dataGridView_LogInfo.Invoke (
new Action<object, string, Log.Severity>(LogtoFormCallback),
new object[] { line, eventTime, severity }
);
} else {
LogtoFormCallback(line, eventTime, severity);
}
}
At the Invoke() line, the thread would hang forever. The solution was to replace it with BeginInvoke() instead.
EDIT: Also, my example was/is quite poor for this. I thought I didn't understand threads at a fundamental level, and that my examples would have been enough. Hopefully someone googles this though and has this same cause, and can try this solution.
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
This is my main form class and inside i have Stop button click event:
public partial class MainWin : Form
{
private Job job = new...
private void btnStop_Click(object sender, EventArgs e)
{
job.state = true;
}
}
When my stop button clicked i change my job class member from false to true and what i want to do is when this variable changed to true i want to access to specific method inside job class and do something.
public class Job
{
public bool state { get; set; }
private void processFile() // i want access to this method in order to change other class state
{
// do work
}
}
how can i do it ?
It's really hard to tell what you exactly mean, but one way to invoke a method when the property is set would be to expand the auto property out and do exactly that.
public class Job
{
private bool state;
public bool State
{
get { return this.state; }
set
{
this.state = value;
processFile();
}
private void processFile()
{
// do work
}
}
However, just guessing and seeing this little bit of code, you might want to redesign how you're doing things.
If really don't want to expose you private method, you can do something like this:
public class Job
{
private bool state;
public bool State
{
get
{
return state;
}
set
{
if (state != value)
{
state = value;
OnStateChanged();
}
}
}
private void OnStateChanged()
{
if (state) // or you could use enum for state
Run();
else
Stop();
}
private void Run()
{
// run
}
private void Stop()
{
// stop
}
}
But you should really consider creating public Job.Run method and leaving Job.State readonly. If you want the object to perform some operations, the methods will be more suitable for this.
Create the Job class like this:
public class Job
{
private bool _isRunning = false;
public bool IsRunning { get { return _isRunning; } }
public void StartProcessing()
{
if (_isRunning)
{
// TODO: warn?
return;
}
ProcessFile();
}
public void StopProcessing()
{
if (!_isRunning)
{
// TODO: warn?
return;
}
// TODO: stop processing
}
private void ProcessFile()
{
_isRunning = true;
// do your thing
_isRunning = false;
}
}
Then consume it like this:
public partial class MainWin : For
{
private Job _job = new Job();
private void StartButton_Click(object sender, EventArgs e)
{
if(!_job.IsRunning)
{
_job.StartProcessing();
}
}
private void StopButton_Click(object sender, EventArgs e)
{
if(_job.IsRunning)
{
_job.StopProcessing();
}
}
}
Thread safety left out as exercise.
I have searched for creating a Singleton object for a window in WPF.
public static Test DefInstance
{
get
{
if (formDefInstance == null) // formDefInstance.IsDisposed
{
initializingDefInstance = true;
formDefInstance = new cas18();
initializingDefInstance = false;
}
return formDefInstance;
}
set { formDefInstance = value; }
}
But the forDefInstance.IsDisposed is not working and throwing an error.
Any Idea regarding this?
I think everyone should take a look at Jon Skeet's C# In Depth site. If only to read and permanently burn into their brains the singleton patter a-la C#.
http://csharpindepth.com/Articles/General/Singleton.aspx
In your scenario, try to implement this (thread safe, non-lazy):
public sealed class DefInstance
{
private static readonly DefInstance instance = new DefInstance();
static DefInstance()
{
}
private DefInstance()
{
}
public static DefInstance Instance
{
get
{
return instance;
}
}
}
There are also Lazy<T> implementions and various other implementations of the pattern in that site.
I don't know if it's what you want to do but it works for me :
private static MyWindow _defInstance;
public static MyWindow DefInstance
{
get
{
if (null == _defInstance)
{
_defInstance = new MyWindow();
}
return _defInstance;
}
}
In MyWindow code :
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
this.Visibility = Visibility.Hidden;
e.Cancel = true;
}
To use it :
DefInstance.Show();
Then, only one window is display and you use one instance of your window.
you can achieve this by implementing following method
private static volatile DefInstance instance;
private static object syncRoot = new Object();
private DefInstance() {}
public static DefInstance Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new DefInstance();
}
}
return instance;
}
}
before posting the question i did my research for 10 days so really hope someone can shed some light into solving this issue.
The issue is that any bindable control, does not update once the binding list from singleton class is changed. This is a common issue on multi-threaded apps. Most if not all solutions offer suggestions where the bindlinglist or collection is initialized from parent thread, and then some invocation to be made. Not what i'm looking for. The same issue persist if static class is used instead of singleton.
Basically, the application triggers some Tasks, which in turn create object(s) on different business classes. These objects post messages into the bindinglist, which should update the UI listbox, but does not. And yes, the message object is in the list, and binding after the TASK finished works (items displayed). Locking/unlocking object(s) access is also not an issue.
Appreciate any suggestions/solutions
A trimmed down version of business objects:
namespace MyNameSpace
{
public class Message
{
private string messageSummary;
public Message() { }
public string MessageSummary
{
set { messageSummary = value; }
get { return messageSummary; }
}
}
}
A trimmed down version of another class doing some ops:
namespace MyNameSpace
{
public class WorkDoingClass
{
public WorkDoingClass() { }
public void DoSomeWork()
{
//some routines
Message messageObj = new Message();
messageObj.MessageSummary = "DoSOmrWork Finished";
}
public void DoSomeOtherWork()
{
//some routines
Message messageObj = new Message();
messageObj.MessageSummary = "DoSomeOtherWork Finished";
AllMessages.Instance.AllMessagesBindingList.Add(messageObj);
}
}
}
Singleton:
namespace MyNameSpace
{
public sealed class AllMessages
{
private static readonly AllMessages _instance = new AllMessages();
private BindingList<Message> _allMessagesBL;
public WorkDoingClass() { _allMessagesBL = new BindingList<Message>(); }
public static AllMessages Instance
{
get { return _instance; }
}
public BindingList<Message> AllMessagesBindingList
{
get { return _allMessagesBL};
}
}
}
This is also a trimmed down version from where calls start:
namespace MyNameSpace
{
public partial class Form1 : Form
{
private Task _TaskSqlData;
private CancellationTokenSource cTokenSourceSql;
public Form1()
{
InitializeComponent();
listBox1.DataSource = AllMessages.Instance.AllMessagesBindingList;
listBox1.DisplayMember = "MessageSummary";
}
private void button1_Click(object sender, EventArgs e)
{
cTokenSourceSql = new CancellationTokenSource();
var tokenSqlData = cTokenSourceSql.Token;
if (this._TaskSqlData != null)
{
if (this._TaskSqlData.Status == TaskStatus.Running)
this.cTokenSourceSql.Cancel();
this._TaskSqlData.Dispose();
this._TaskSqlData = null;
}
_TaskSqlData = Task.Factory.StartNew(()
=> StartDoingWork(this, tokenSqlData, null), tokenSqlData);
}
public void StartDoingWork(object sender, CancellationToken ct, EventArgs e)
{
if (ct.IsCancellationRequested)
ct.ThrowIfCancellationRequested();
WorkDoingClass work = new WorkDoingClass();
work.DoSomeOtherWork();
}
Your problem is that the thread(the main UI thread) making the listbox is different from the thread(the worker thread) modifying the collection.
Try the following code. It could solve your issue. I use SynchronizationContext to synchronize the two threads, which serves as the same function with Control.Invoke().
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Task _TaskSqlData;
private CancellationTokenSource cTokenSourceSql;
WorkDoingClass _work;
public Form1()
{
InitializeComponent();
listBox1.DataSource = AllMessages.Instance.AllMessagesBindingList;
listBox1.DisplayMember = "MessageSummary";
_work = new WorkDoingClass(SynchronizationContext.Current);
}
private void button1_Click(object sender, EventArgs e)
{
cTokenSourceSql = new CancellationTokenSource();
var tokenSqlData = cTokenSourceSql.Token;
if (this._TaskSqlData != null)
{
if (this._TaskSqlData.Status == TaskStatus.Running)
this.cTokenSourceSql.Cancel();
this._TaskSqlData.Dispose();
this._TaskSqlData = null;
}
_TaskSqlData = Task.Factory.StartNew(()
=> StartDoingWork(this, tokenSqlData, null), tokenSqlData);
}
public void StartDoingWork(object sender, CancellationToken ct, EventArgs e)
{
if (ct.IsCancellationRequested)
ct.ThrowIfCancellationRequested();
_work.DoSomeOtherWork();
}
}
public class Message
{
private string messageSummary;
public Message() { }
public string MessageSummary
{
set { messageSummary = value; }
get { return messageSummary; }
}
}
public class WorkDoingClass
{
private SynchronizationContext _syncContext;
public WorkDoingClass() { }
public WorkDoingClass(SynchronizationContext _syncContext)
{
// TODO: Complete member initialization
this._syncContext = _syncContext;
}
public void DoSomeWork()
{
//some routines
Message messageObj = new Message();
messageObj.MessageSummary = "DoSOmrWork Finished";
}
public void DoSomeOtherWork()
{
_syncContext.Send(DoWork, null);
}
private static void DoWork(object arg)
{
//some routines
Message messageObj = new Message();
messageObj.MessageSummary = "DoSomeOtherWork Finished";
AllMessages.Instance.AllMessagesBindingList.Add(messageObj);
}
}
public sealed class AllMessages
{
private static readonly AllMessages _instance = new AllMessages();
private BindingList<Message> _allMessagesBL;
public AllMessages() { _allMessagesBL = new BindingList<Message>(); }
public static AllMessages Instance
{
get { return _instance; }
}
public BindingList<Message> AllMessagesBindingList
{
get { return _allMessagesBL; }
}
}
}