Multi threading hangs UI? - c#

I am using multi threading to make tasks goes faster and smooth, when results increase into richtextbox the UI start hanging don't know why, I creating a webbrowser in thread and doing some other stuff in a single thread kind !
Using Thread as STA ( Single thread kind )
here is a snippet of the code !
foreach (string line in URLLMemoRichTxt.Lines)
{
string href = line;
if (href.Trim() != string.Empty)
{
//XtraMessageBox.Show(href);
if (StopGettingInnerLink == true)
{
AddLog("Getting links has been stopped successfully!");
StopGettingInnerLink = true;
break;
}
else if (StopGettingInnerLink == false)
{
AddLog("Getting links from " + href);
runBrowserThread( new Uri(href));
await Task.Delay(5000);
AddLog("Giving the tool some rest for 5 seconds ! ");
}
}
}
private void runBrowserThread(Uri url)
{
browserth = new Thread(() => {
var br = new WebBrowser();
br.ScriptErrorsSuppressed = true;
br.DocumentCompleted += browser_DocumentCompleted;
br.Navigate(url);
Application.Run();
});
browserth.SetApartmentState(ApartmentState.STA);
browserth.Start();
}
void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var br = sender as WebBrowser;
string currentURL = br.Url.ToString();
if (br.Url == e.Url)
{
HtmlElementCollection acollection = br.Document.GetElementsByTagName("a");
foreach (HtmlElement a in acollection)
{
string href = a.GetAttribute("href");
if (URLLMemoRichTxt.InvokeRequired)
{
URLLMemoRichTxt.Invoke((MethodInvoker)delegate ()
{
if (!URLList.Contains(href) && href.Trim() != string.Empty && !href.Contains(".jpg") && !href.Contains(".png") && !href.Contains(".gif") && !href.Contains(".jpeg"))
{
URLList.Add(href);
// URLListView.Items.Add(href);
// adding new link ino listview !
// URLListCountLBL.Text = URLListView.Items.Count.ToString();
URLLMemoRichTxt.Text += href + "\n";
URLListCountLBL.Text = URLLMemoRichTxt.Lines.Length.ToString();
// runbrowserinthread(href);
}
});
}
else
{
if (!URLList.Contains(href) && href.Trim() != string.Empty && !href.Contains(".jpg") && !href.Contains(".png") && !href.Contains(".gif") && !href.Contains(".jpeg"))
{
URLList.Add(href);
// URLListView.Items.Add(href);
URLLMemoRichTxt.Text += href + "\n";
URLListCountLBL.Text = URLLMemoRichTxt.Lines.Length.ToString();
// GetInnerLink(href);
}
}
}
AddLog("All links has been scrapped successfully for \r\n" + currentURL);
Application.ExitThread(); // Stops the thread
}
}

Already found a solution myself :
replaced :
URLLMemoRichTxt.Text += href + "\n";
With :
URLLMemoRichTxt.AppendText(Environment.NewLine + href);

Related

Multithread using backgroundworker and event handler

I'm developing a sample program to connect multiple device using backgroundworker. Each device connected will be add to the list as new object. After finished connecting all the devices, i wanted to add an event handler for each connected devices. The problem that i'm facing now is the event handler doesn't firing at all. Below are the sample codes.
The Connect click button event :
private void btnConnect_Click(object sender, EventArgs e)
{
using (BackgroundWorker m_oWorker = new BackgroundWorker())
{
m_oWorker.DoWork += delegate (object s, DoWorkEventArgs args)
{
int iIpStart = 0;
int iIpEnd = 0;
string strIp1 = string.Empty;
string strIp2 = string.Empty;
list.Clear();
string[] sIP1 = txtIpStart.Text.Trim().ToString().Split('.');
string[] sIP2 = txtIpEnd.Text.Trim().ToString().Split('.');
iIpStart = Convert.ToInt32(sIP1[3]);
iIpEnd = Convert.ToInt32(sIP2[3]);
strIp1 = sIP1[0] + "." + sIP1[1] + "." + sIP1[2] + ".";
strIp2 = sIP2[0] + "." + sIP2[1] + "." + sIP2[2] + ".";
Ping ping = new Ping();
PingReply reply = null;
int iIncre = 0;
int iVal = (100 / (iIpEnd - iIpStart));
for (int i = iIpStart; i <= iIpEnd; i++)
{
Thread.Sleep(100);
string strIpconnect = strIp1 + i.ToString();
Console.Write("ip address : " + strIpconnect + ", status: ");
reply = ping.Send(strIpconnect);
if (reply.Status.ToString() == "Success")
{
if (ConnectDevice(strIpconnect))
{
strLastDevice = strIpconnect + " Connected";
isconnected = true;
}
else
{
isconnected = false;
}
}
else
{
isconnected = false;
}
m_oWorker.ReportProgress(iIncre);
iIncre = iIncre + iVal;
}
m_oWorker.ReportProgress(100);
};
m_oWorker.ProgressChanged += new ProgressChangedEventHandler(m_oWorker_ProgressChanged);
m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(m_oWorker_RunWorkerCompleted);
m_oWorker.WorkerReportsProgress = true;
m_oWorker.WorkerSupportsCancellation = true;
m_oWorker.RunWorkerAsync();
}
}
ConnectDevice function method. Connected device will be added to the list :
protected bool ConnectDevice(string sIP)
{
try
{
NewSDK sdk = new NewSDK();
if (sdk.Connect() == true)
{
list.Add(new objSDK { sdk = sdk, ipaddress = sIP });
return true;
}
else
{
}
}
catch() {}
return false;
}
the Backgroundworker :
void m_oWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//If it was cancelled midway
if (e.Cancelled)
{
lblStatus.Text = "Task Cancelled.";
}
else if (e.Error != null)
{
lblStatus.Text = "Error while performing background operation.";
}
else
{
lblStatus.Text = "Task Completed...";
btnListen.Enabled = true;
}
}
void m_oWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Here you play with the main UI thread
progressBar1.Value = e.ProgressPercentage;
lblStatus.Text = "Processing......" + progressBar1.Value.ToString() + "%";
if (isconnected)
{
listBox2.Items.Add(strLastDevice);
string[] ssplit = sDeviceInfo.Split(';');
foreach (string sword in ssplit)
{
listBox1.Items.Add(sword);
}
}
}
The function to attached event :
private void RegisterEvent()
{
foreach (objSDK obj in list)
{
obj.sdk.OnTransaction += () =>
{
listBox1.Items.Add("ip : " + obj.IP + " transaction");
};
}
}
You have declared m_oWorker as a local variable. I'm guessing this was a mistake ( the m_ prefix should only be used for class member variables)?
Also, you declared it within a using statement, meaning that it that the framework will call Dispose() on it at the end of the using block. Even if you held on to a reference to it (and I don't think you do) it still means its resources will be deallocated, which is probably why it isn't handling any events.
I try another workaround by using thread and task and work perfectly. Thanks for all response

I need to make a post after the page loaded, but I'm using Thread

I'm doing a post on a page that works as follows:
Click on a link and opens a text box, then accurately loaded to another post to save the contents of the input text.
Follows the code I'm using.
private void btnRobo_Click(object sender, EventArgs e)
{
// string[] paginasArray = new string[] { txtPaginas.Text };
string[] paginasArray = txtPaginas.Text.Split('\n');
foreach (string s in paginasArray)
{
// webBrowser1.Navigate(s);
if (s.Trim() != "")
runBrowserThread(new Uri(s));
}
}
private void runBrowserThread(Uri url)
{
var th = new Thread(() =>
{
var br = new WebBrowser();
br.DocumentCompleted += browser_DocumentCompleted;
br.Navigate(url);
Application.Run();
});
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var br = sender as WebBrowser;
if (br.Url == e.Url)
{
Console.WriteLine("Natigated to {0}", e.Url);
webBrowser1.Navigate(e.Url);
//ExecutaPostagem();
HtmlElementCollection elements = br.Document.GetElementsByTagName("input");
foreach (HtmlElement currentElement in elements)
{
if ((currentElement.GetAttribute("type") == "submit") && (currentElement.Name == "view_post"))
{
string postagem = txtPublicacao.Text;
HtmlElement elea = br.Document.GetElementById("u_0_0");
if (elea != null) elea.SetAttribute("value", postagem);
currentElement.InvokeMember("click");
Thread.Sleep(1000);
fullyLoaded = false;
}
}
Application.ExitThread(); // Stops the thread
}
}
After the page was loaded by the browser_DocumentCompleted need to run the code below, but how will I run the code if I am using thread and I'm no longer using the event browser_DocumentCompleted
var links = br.Document.GetElementsByTagName("A");
// var links = webBrowser1.Document.All;
foreach (HtmlElement link in links)
{
// if (link.InnerText != null)
if ((link.InnerText != null) && (link.InnerText.Contains("comentários")))
{
Thread.Sleep(1000);
MessageBox.Show(link.InnerText.ToString()); //.Contains("comentários").ToString());
//MessageBox.Show(link.GetAttribute("InnerText"));
// MessageBox.Show(link.GetAttribute("className"));
link.InvokeMember("click");
break;
}
}
Don't use WebClient or Threads at all.
Instead use http://restsharp.org/ to make the call to your website. Restsharp is super easy to work with.
And instead of using Threads use async/await (restsharp also supports this) to modify the UI later.

how to release thread is required in multiple thread?

As title, how to release thread is required in multiple thread ?
Ex : I have 5 thread is waiting. I only want thread position 3 is released
I use autoresetevent/manualresetevent/monitor.wait and monitor.pulse but all release thread follow FIFO
help me !!!
UPDATED:
This is form1:
private BackgroundWorker[] threadArray;
public static ManualResetEvent _manualResetEvent = new ManualResetEvent(false);
private void btn_Start_Scraping_Click(object sender, EventArgs e)
{
threadArray = new BackgroundWorker[listView_Site.Items.Count];
for (var f = 0; f < listView_Site.Items.Count; f++)
{
threadArray[f] = new BackgroundWorker();
threadArray[f].DoWork += new DoWorkEventHandler(BackgroundWorkerFilesDoWork);
threadArray[f].RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorkerFilesRunWorkerCompleted);
threadArray[f].ProgressChanged += new ProgressChangedEventHandler(BackgroundWorkerFilesProgressChanged);
threadArray[f].WorkerReportsProgress = true;
threadArray[f].WorkerSupportsCancellation = true;
threadArray[f].RunWorkerAsync(listView_Site.Items[f].Tag.ToString());
}
}
private void BackgroundWorkerFilesDoWork(object sender, DoWorkEventArgs e)
{
....// all above code is fine
requestCaptcha = (HttpWebRequest)WebRequest.Create(uriCaptchaPage);
requestCaptcha.Pipelined = true;
requestCaptcha.KeepAlive = true;
requestCaptcha.AllowAutoRedirect = false;
//request.Proxy = null;
requestCaptcha.Timeout = 60000;
requestCaptcha.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
requestCaptcha.CookieContainer = sessionID;
request.ServicePoint.Expect100Continue = false;
requestCaptcha.Method = "GET";
requestCaptcha.Referer = uriloginPage.AbsoluteUri;
//get response.
responseCaptcha = (HttpWebResponse)requestCaptcha.GetResponse();
Stream imagestream = responseCaptcha.GetResponseStream();
if (imagestream != null)
{
Image image = Image.FromStream(imagestream);
if (Directory.Exists(Application.StartupPath + "\\Captcha") == false)
{
Directory.CreateDirectory(Application.StartupPath + "\\Captcha");
}
switch (responseCaptcha.ContentType)
{
case "image/jpeg":
{
saveLocation += ".jpg";
if (File.Exists(saveLocation))
{
File.Delete(saveLocation);
}
image.Save(saveLocation,ImageFormat.Jpeg);
break;
}
case "image/gif":
{
saveLocation += ".gif";
if (File.Exists(saveLocation))
{
File.Delete(saveLocation);
}
image.Save(saveLocation, ImageFormat.Gif);
break;
}
case "image/png":
{
saveLocation += ".png";
if (File.Exists(saveLocation))
{
File.Delete(saveLocation);
}
image.Save(saveLocation, ImageFormat.Png);
break;
}
}
//show form2 to enter captcha
lock (_lockObj)
{
if (Application.OpenForms.OfType<frmCaptchaQuestion>().Any() == false)
{
DoOnUIThread(delegate()
{
_formCaptchaQuestion.CreatePanelCaptcha(uriloginPage, saveLocation,idHomePage);
_formCaptchaQuestion.Show();
});
}
else
{
DoOnUIThread(() => _formCaptchaQuestion.CreatePanelCaptcha(uriloginPage, saveLocation,idHomePage));
}
}
//wait and get captcha from form2 and only run thread is required
//this is my problem <<<<========================================
lock (_lockObj)
{
//_manualResetEvent.WaitOne(30000);
//_manualResetEvent.Reset();
//if (clsValueStatic.CaptchaText != null)
//{
// foreach (var id in clsValueStatic.CaptchaText)
// {
while (!_go)
{
Monitor.Wait(_lockObj);
}
// }
//}
}
requestCaptcha = (HttpWebRequest)WebRequest.Create(uriActionLoginPage);
requestCaptcha.Pipelined = true;
requestCaptcha.KeepAlive = true;
requestCaptcha.AllowAutoRedirect = false;
//request.Proxy = null;
requestCaptcha.Timeout = 60000;
requestCaptcha.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
requestCaptcha.CookieContainer = sessionID;
request.ServicePoint.Expect100Continue = false;
requestCaptcha.Method = "GET";
}
Form2:
private void textBoxCaptcha_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
var textBox = sender as TextBoxX;
if (textBox != null)
{
clsValueStatic.CaptchaText = textBox.Text.Trim();
textBox.Parent.Parent.Dispose();
frmScrapingAnalysis._manualResetEvent.Set();
}
}
}
PS : Form1 have 1 button to start multiple backgroundworker and show form2 then all backgroundworker wait to get text captcha of textbox from form2
my way want when user enter text of backgroundworker is shown on form2 then only the backgroundworker is released. All other backgroundworker still wait

APM pattern, Await Async

I need help on how to use APM pattern, i am now reading some articles, but i am afraid i don't have much time. What i really want is to get all the persons(data from db) then get the photos and put it on a autocompletebox
Code:
void listanomesautocomplete(object sender, ServicosLinkedIN.queryCompletedEventArgs e)
{
if (e.Result[0] != "")
{
for (int i = 0; i < e.Result.Count(); i = i + 3)
{
Pessoa pessoa = new Pessoa();
pessoa.Nome = e.Result[i];
pessoa.Id = Convert.ToInt32(e.Result[i + 1]);
if (e.Result[i + 2] == "")
pessoa.Imagem = new BitmapImage(new Uri("Assets/default_perfil.png", UriKind.Relative));
else
{
ServicosLinkedIN.ServicosClient buscaimg = new ServicosLinkedIN.ServicosClient();
buscaimg.dlFotoAsync(e.Result[i + 2]);
buscaimg.dlFotoCompleted += buscaimg_dlFotoCompleted;//when this completes it saves into a public bitmapimage and then i save it into pessoa.Imagem
//basicly, what happends, the listadlfotosasync only happends after this function
//what i want is to wait until it completes and have into the class novamsg
pessoa.Imagem = img;//saving the photo from dlFotoAsync
}
listaPessoas.Add(pessoa);
}
tbA_destinatario.ItemsSource = null;
tbA_destinatario.ItemsSource = listaPessoas;
BackgroundWorker listapessoas = new BackgroundWorker();
listapessoas.DoWork += listapessoas_DoWork;
listapessoas.RunWorkerAsync();
tb_lerdestmsg.Text = "";
}
else
{
tbA_destinatario.ItemsSource = null;
tb_lerdestmsg.Text = "Não encontrado";
}
}
There are a few things to address here:
The listanomesautocomplete event handler should be async. Handle and report all exceptions possibly thrown inside it (generally, this rule applies to any event handler, but it's even more important for async event handlers, because the asynchronous operation inside your handler continues outside the scope of the code which has fired the event).
Register the dlFotoCompleted event handler first, then call dlFotoAsync. Do not assume that dlFotoAsync will be always executed asynchronously.
Think about the case when another listanomesautocomplete is fired while the previous operation is still pending. This well may happen as a result of the user's action. You may want to cancel and restart the pending operation (like this), or just queue a new one to be executed as soon as the pending one has completed (like this).
Back to the question, it's the EAP pattern that you need to wrap as Task, not APM. Use TaskCompletionSource for that. The relevant part of your code may look like this:
async void listanomesautocomplete(object sender, ServicosLinkedIN.queryCompletedEventArgs e)
{
if (e.Result[0] != "")
{
for (int i = 0; i < e.Result.Count(); i = i + 3)
{
Pessoa pessoa = new Pessoa();
pessoa.Nome = e.Result[i];
pessoa.Id = Convert.ToInt32(e.Result[i + 1]);
if (e.Result[i + 2] == "")
pessoa.Imagem = new BitmapImage(new Uri("Assets/default_perfil.png", UriKind.Relative));
else
{
// you probably want to create the service proxy
// outside the for loop
using (ServicosLinkedIN.ServicosClient buscaimg = new ServicosLinkedIN.ServicosClient())
{
FotoCompletedEventHandler handler = null;
var tcs = new TaskCompletionSource<Image>();
handler = (sHandler, eHandler) =>
{
try
{
// you can move the code from buscaimg_dlFotoCompleted here,
// rather than calling buscaimg_dlFotoCompleted
buscaimg_dlFotoCompleted(sHandler, eHandler);
tcs.TrySetResult(eHandler.Result);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
};
try
{
buscaimg.dlFotoCompleted += handler;
buscaimg.dlFotoAsync(e.Result[i + 2]);
// saving the photo from dlFotoAsync
pessoa.Imagem = await tcs.Task;
}
finally
{
buscaimg.dlFotoCompleted -= handler;
}
}
}
listaPessoas.Add(pessoa);
}
tbA_destinatario.ItemsSource = null;
tbA_destinatario.ItemsSource = listaPessoas;
BackgroundWorker listapessoas = new BackgroundWorker();
listapessoas.DoWork += listapessoas_DoWork;
listapessoas.RunWorkerAsync();
tb_lerdestmsg.Text = "";
}
else
{
tbA_destinatario.ItemsSource = null;
tb_lerdestmsg.Text = "Não encontrado";
}
}
void listanomesautocomplete(object sender, ServicosLinkedIN.queryCompletedEventArgs e)
{
if (e.Result[0] != "")
{
List<Pessoa> listaPessoas = new List<Pessoa>();
for (int i = 0; i < e.Result.Count(); i = i + 3)
{
Pessoa pessoa = new Pessoa();
pessoa.Nome = e.Result[i];
pessoa.Id = Convert.ToInt32(e.Result[i + 1]);
if (e.Result[i + 2] == "")
pessoa.Imagem = new BitmapImage(new Uri("Assets/default_perfil.png", UriKind.Relative));
else
{
ServicosLinkedIN.ServicosClient buscaimg = new ServicosLinkedIN.ServicosClient();
buscaimg.dlFotoAsync(e.Result[i + 2]);
//THIS ACTUALLY WORKS!!!
//THE THREAD WAITS FOR IT!
buscaimg.dlFotoCompleted += (s, a) =>
{
pessoa.Imagem = ConvertToBitmapImage(a.Result);
};
}
listaPessoas.Add(pessoa);
}
if (tbA_destinatario.ItemsSource == null)
{
tbA_destinatario.ItemsSource = listaPessoas;
}
tb_lerdestmsg.Text = "";
}
else
{
tbA_destinatario.ItemsSource = null;
tb_lerdestmsg.Text = "Não encontrado";
}
}
Man, i am not even mad, i am amazed.
Noseratio, you answer gave me an idea and it actually worked!!
Thanks a lot man, i can't thank you enough ;)

Cross thread problem?

My error
Cross-thread operation not
valid: Control 'MailTree' accessed
from a thread other than the thread it
was created on.
with my code
My idea is when SaveMail method has finish store 1 mes then add this mes to listview.
private delegate int SaveMailDelegate(ImapX.Message mes);
public int SaveMail(ImapX.Message mess)
{
if (!File.Exists("D:\\" + Username + "\\" + MailTree.SelectedNode.Text + "\\" + mes.MessageUid.ToString() + ".eml"))
{
mess.Process();
mess.SaveAsEmlToFile("D:\\" + Username + "\\" + MailTree.SelectedNode.Text + "\\", mes.MessageUid.ToString()); //Store messages to a Location
}
// mes.MessageUid=mess.MessageUid;
return 1;
}
Mime EncodingMail(string NodeName,string focusitem)
{
Mime m = new Mime();
m=Mime.Parse("D:\\" + Username+ "\\"+NodeName+"\\"+focusitem+".eml");
return m;
}
private void AddMesToMailList()
{
ListViewItem item = new ListViewItem();
Mime m = EncodingMail(MailTree.SelectedNode.Text, mes);
item.Text = mes.MessageUid.ToString();
item.SubItems.Add(m.MainEntity.Subject);
ReturnMime(m);
if (mailfromname != null)
item.SubItems.Add(mailfromname);
else item.SubItems.Add(mailfrom);
item.SubItems.Add(m.MainEntity.Date.ToString());
item.SubItems.Add(mailfrom);
MailList.Items.Add(item);
}
private void SaveMailDone(IAsyncResult iar)
{
SaveMailDelegate del = iar.AsyncState as SaveMailDelegate;
if (del != null)
{
int result = del.EndInvoke(iar);
AddMesToMailList();
}
}
private void MailTree_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
{
MailList.Items.Clear();
for (int i = 0; i < client.Folders.Count; i++)
{
(ContextMenuListView.Items[1] as ToolStripMenuItem).DropDownItems[i].Click += new EventHandler(MainForm_Click);
}
if (MailTree.SelectedNode.Text == Username)
{
webBrowser1.Visible = false;//webBrowser1.DocumentText = "Hello Baby";
AttachmentList.Visible = false;
groupBox1.Visible = false;
}
else
{
webBrowser1.Visible = true;
groupBox1.Visible = true;
try
{
messages = client.Folders[MailTree.SelectedNode.Text].Search("ALL", false); // Search mail in your choossen Folder
AmoutOfMail = messages.Count(); //Amout of Mail in this Folder
for (int i = 0; i < AmoutOfMail; i++)
{
mes=messages[i];
SaveMailDelegate del = new SaveMailDelegate(this.SaveMail);
del.BeginInvoke(mes, new AsyncCallback(this.SaveMailDone), del);
}
}
catch (Exception)
{ }
}
}
You cannot directly access a control from another thread, you will have to invoke it.
private delegate void ControlCallback(string s);
public void CallControlMethod(string text)
{
if (control.InvokeRequired)
{
ControlCallback call = new ControlCallback((s) =>
{
// do control stuff
});
control.Invoke(call, new object[] { text });
}
else
{
// do control stuff
}
}
you can't access the UI on a different thread than what it was created on. From inside your secondary thread (the one that runs your callback handler) you will need to call Form.BeginInvoke to register a method that will be run on the UI thread. From that method you can update your UI controls
I think AddMesToMailList() is trying to modify the view elements but it is on a wrong thread.
Try something like this
void AddMesToMailList()
{
if (this.InvokeRequired)
{
this.BeginInvoke(new Action(AddMesToMailList));
return;
}
// do stuff that original AddMesToMailList() did.
}
EDIT:
SaveMail is a little complicated as it has a return value but you can try this
public int SaveMail(ImapX.Message mess)
{
if(this.InvokeRequired)
{
return (int) this.Invoke(
new Func<ImapX.Message, int>( m => SaveMail(mess)) );
}
else
{
if (!File.Exists(#"D:\" + Username + "\\" + MailTree.SelectedNode.Text + "\\" + mes.MessageUid.ToString() + ".eml"))
{
mess.Process();
mess.SaveAsEmlToFile(#"D:\" + Username + "\\" + MailTree.SelectedNode.Text + "\\", mes.MessageUid.ToString()); //Store messages to a Location
}
// mes.MessageUid=mess.MessageUid;
return 1;
}
}

Categories

Resources